From 24736a7bc1f08e4af91ac0f49afe3e99e21e2b1c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 17 Feb 2025 19:08:36 -0800 Subject: [PATCH] extract server --- Client/.env.production => .env.production | 0 Client/.eslintrc.cjs => .eslintrc.cjs | 0 .gitignore | 34 +- Client/.gitignore | 28 - Client/README.md | 8 - Client/index.html | 16 - Docker/.gitignore | 11 - Docker/dev/build_images.sh | 26 - Docker/dev/client.Dockerfile | 27 - Docker/dev/docker-compose.yaml | 40 - Docker/dev/mongoDB.Dockerfile | 3 - Docker/dev/redis.Dockerfile | 2 - Docker/dev/server.Dockerfile | 13 - Docker/dist/build_images.sh | 26 - Docker/dist/client.Dockerfile | 33 - Docker/dist/docker-compose.yaml | 46 - Docker/dist/mongoDB.Dockerfile | 3 - Docker/dist/nginx/conf.d/default.conf | 35 - Docker/dist/redis.Dockerfile | 2 - Docker/dist/server.Dockerfile | 13 - Docker/prod/build_images.sh | 26 - Docker/prod/certbot-compose.yaml | 19 - Docker/prod/client.Dockerfile | 29 - Docker/prod/docker-compose.yaml | 57 - Docker/prod/mongo/init/create_users.js | 16 - Docker/prod/mongoDB.Dockerfile | 4 - Docker/prod/nginx/conf.d/cerbot | 15 - Docker/prod/nginx/conf.d/default.conf | 69 - Docker/prod/redis.Dockerfile | 2 - Docker/prod/server.Dockerfile | 15 - Docker/test/build_images.sh | 26 - Docker/test/cerbot-compose.yaml | 19 - Docker/test/client.Dockerfile | 30 - Docker/test/docker-compose.yaml | 57 - Docker/test/mongo/init/create_users.js | 16 - Docker/test/mongoDB.Dockerfile | 4 - Docker/test/nginx/conf.d/certbot | 15 - Docker/test/nginx/conf.d/default.conf | 69 - Docker/test/redis.Dockerfile | 2 - Docker/test/server.Dockerfile | 13 - README.md | 142 +- Server/.dockerignore | 1 - Server/.gitignore | 7 - Server/.mocharc.cjs | 8 - Server/.nycrc | 8 - Server/controllers/authController.js | 523 -- Server/controllers/checkController.js | 154 - Server/controllers/controllerUtils.js | 26 - .../distributedUptimeController.js | 212 - Server/controllers/inviteController.js | 99 - .../maintenanceWindowController.js | 163 - Server/controllers/monitorController.js | 548 -- Server/controllers/notificationController.js | 59 - Server/controllers/queueController.js | 61 - Server/controllers/settingsController.js | 48 - Server/controllers/statusPageController.js | 120 - Server/db/FakeDb.js | 138 - Server/db/models/AppSettings.js | 91 - Server/db/models/Check.js | 83 - Server/db/models/DistributedUptimeCheck.js | 123 - Server/db/models/HardwareCheck.js | 70 - Server/db/models/InviteToken.js | 34 - Server/db/models/MaintenanceWindow.js | 68 - Server/db/models/Monitor.js | 98 - Server/db/models/Notification.js | 91 - Server/db/models/PageSpeedCheck.js | 111 - Server/db/models/RecoveryToken.js | 25 - Server/db/models/StatusPage.js | 70 - Server/db/models/Team.js | 14 - Server/db/models/User.js | 92 - Server/db/mongo/MongoDB.js | 147 - Server/db/mongo/modules/checkModule.js | 301 - .../mongo/modules/distributedCheckModule.js | 26 - .../db/mongo/modules/hardwareCheckModule.js | 58 - Server/db/mongo/modules/inviteModule.js | 89 - .../mongo/modules/maintenanceWindowModule.js | 228 - Server/db/mongo/modules/monitorModule.js | 937 -- .../db/mongo/modules/monitorModuleQueries.js | 721 -- Server/db/mongo/modules/notificationModule.js | 56 - .../db/mongo/modules/pageSpeedCheckModule.js | 47 - Server/db/mongo/modules/recoveryModule.js | 88 - Server/db/mongo/modules/settingsModule.js | 30 - Server/db/mongo/modules/statusPageModule.js | 211 - Server/db/mongo/modules/userModule.js | 232 - Server/db/mongo/utils/seedDb.js | 57 - Server/eslint.config.js | 30 - Server/index.js | 320 - Server/locales/en.json | 154 - Server/locales/en.json.bak | 147 - Server/middleware/handleErrors.js | 22 - Server/middleware/isAllowed.js | 60 - Server/middleware/languageMiddleware.js | 11 - Server/middleware/responseHandler.js | 20 - Server/middleware/verifyJWT.js | 97 - Server/middleware/verifyOwnership.js | 53 - Server/middleware/verifySuperAdmin.js | 68 - Server/nodemon.json | 12 - Server/openapi.json | 2562 ----- Server/package-lock.json | 8334 ----------------- Server/package.json | 56 - Server/routes/authRoute.js | 60 - Server/routes/checkRoute.js | 46 - Server/routes/distributedUptimeRoute.js | 26 - Server/routes/inviteRoute.js | 27 - Server/routes/maintenanceWindowRoute.js | 37 - Server/routes/monitorRoute.js | 87 - Server/routes/notificationRoute.js | 24 - Server/routes/queueRoute.js | 21 - Server/routes/settingsRoute.js | 25 - Server/routes/statusPageRoute.js | 38 - Server/service/emailService.js | 140 - Server/service/jobQueue.js | 606 -- Server/service/networkService.js | 509 - Server/service/notificationService.js | 298 - Server/service/serviceRegistry.js | 35 - Server/service/settingsService.js | 84 - Server/service/statusService.js | 200 - Server/service/stringService.js | 388 - Server/service/translationService.js | 90 - Server/templates/addReview.mjml | 58 - Server/templates/employeeActivation.mjml | 52 - Server/templates/hardwareIncident.mjml | 43 - Server/templates/noIncidentsThisWeek.mjml | 66 - Server/templates/passwordReset.mjml | 56 - Server/templates/serverIsDown.mjml | 45 - Server/templates/serverIsUp.mjml | 42 - Server/templates/welcomeEmail.mjml | 57 - .../tests/controllers/authController.test.js | 975 -- .../tests/controllers/checkController.test.js | 379 - .../tests/controllers/controllerUtils.test.js | 161 - .../controllers/inviteController.test.js | 205 - .../maintenanceWindowController.test.js | 422 - .../controllers/monitorController.test.js | 1124 --- .../tests/controllers/queueController.test.js | 181 - .../controllers/settingsController.test.js | 112 - .../controllers/statusPageController.test.js | 130 - Server/tests/db/checkModule.test.js | 593 -- Server/tests/db/hardwareCheckModule.test.js | 139 - Server/tests/db/inviteModule.test.js | 110 - .../tests/db/maintenanceWindowModule.test.js | 283 - Server/tests/db/monitorModule.test.js | 1931 ---- Server/tests/db/notificationModule.test.js | 80 - Server/tests/db/pageSpeedCheckModule.test.js | 66 - Server/tests/db/recoveryModule.test.js | 179 - Server/tests/db/settingsModule.test.js | 59 - Server/tests/db/statusPageModule.test.js | 73 - Server/tests/db/userModule.test.js | 304 - Server/tests/services/emailService.test.js | 212 - Server/tests/services/jobQueue.test.js | 834 -- Server/tests/services/networkService.test.js | 455 - .../services/notificationService.test.js | 302 - Server/tests/services/settingsService.test.js | 142 - Server/tests/services/statusService.test.js | 259 - Server/tests/utils/dataUtils.test.js | 67 - Server/tests/utils/imageProcessing.test.js | 57 - Server/tests/utils/logger.test.js | 139 - Server/tests/utils/messages.test.js | 29 - Server/tests/utils/utils.test.js | 50 - Server/utils/dataUtils.js | 92 - Server/utils/demoMonitors.json | 22 - Server/utils/demoMonitorsOld.json | 1271 --- Server/utils/imageProcessing.js | 25 - Server/utils/logger.js | 95 - Server/utils/utils.js | 35 - Server/validation/joi.js | 605 -- docs/diagrams/Bluewave Uptime Auth Flow.pdf | Bin 20480 -> 0 bytes docs/diagrams/Bluewave Uptime Auth Flow.png | Bin 47938 -> 0 bytes .../Bluewave Uptime High Level Overview.pdf | Bin 17242 -> 0 bytes .../Bluewave Uptime High Level Overview.png | Bin 193734 -> 0 bytes .../Bluewave Uptime JobQueue Flow.pdf | Bin 36396 -> 0 bytes .../Bluewave Uptime JobQueue Flow.png | Bin 325174 -> 0 bytes .../diagrams/Bluewave Uptime Monitor Flow.pdf | Bin 20107 -> 0 bytes .../diagrams/Bluewave Uptime Monitor Flow.png | Bin 49157 -> 0 bytes Client/env.sh => env.sh | 0 .../Screenshot 2024-10-10 at 10.37.36 PM.png | Bin 308418 -> 0 bytes guides/users-guide/maintenance-mode.md | 35 - index.html | 17 +- Client/package-lock.json => package-lock.json | 0 Client/package.json => package.json | 0 .../public => public}/checkmate_favicon.svg | 0 {Client/src => src}/App.jsx | 0 .../Components/ActionsMenu/index.jsx | 0 .../src => src}/Components/Alert/index.css | 0 .../src => src}/Components/Alert/index.jsx | 0 .../Components/Animated/PulseDot.jsx | 0 .../src => src}/Components/Avatar/index.css | 0 .../src => src}/Components/Avatar/index.jsx | 0 .../Components/Breadcrumbs/index.css | 0 .../Components/Breadcrumbs/index.jsx | 0 .../Components/Charts/AreaChart/index.jsx | 0 .../Components/Charts/BarChart/index.css | 0 .../Components/Charts/BarChart/index.jsx | 0 .../Components/Charts/ChartBox/index.jsx | 0 .../Components/Charts/CustomGauge/index.css | 0 .../Components/Charts/CustomGauge/index.jsx | 0 .../Components/Charts/LegendBox/index.jsx | 0 .../Charts/MonitorDetailsAreaChart/index.css | 0 .../Charts/MonitorDetailsAreaChart/index.jsx | 0 .../Charts/StatusPageBarChart/index.jsx | 0 .../Charts/Utils/chartUtilFunctions.js | 0 .../Components/Charts/Utils/chartUtils.jsx | 0 .../Components/Charts/Utils/gradientUtils.jsx | 0 .../src => src}/Components/Check/Check.jsx | 0 .../src => src}/Components/Check/check.css | 0 .../Components/CircularCount/index.jsx | 0 .../Components/ConfigBox/index.jsx | 0 .../Components/Dialog/genericDialog.jsx | 0 .../src => src}/Components/Dialog/index.jsx | 0 {Client/src => src}/Components/Dot/index.jsx | 0 .../src => src}/Components/Fallback/index.css | 0 .../src => src}/Components/Fallback/index.jsx | 0 .../GenericFallback/NetworkError.jsx | 0 .../Components/GenericFallback/index.jsx | 0 .../Components/HOC/withAdminCheck.jsx | 0 .../src => src}/Components/Heading/index.jsx | 0 {Client/src => src}/Components/Host/index.jsx | 0 .../Components/HttpStatusLabel/index.jsx | 0 .../src => src}/Components/IconBox/index.jsx | 0 .../src => src}/Components/Image/index.jsx | 0 .../Components/Inputs/Checkbox/index.css | 0 .../Components/Inputs/Checkbox/index.jsx | 0 .../Components/Inputs/ColorPicker/index.jsx | 0 .../Components/Inputs/Image/index.css | 0 .../Components/Inputs/Image/index.jsx | 0 .../Components/Inputs/Radio/index.css | 0 .../Components/Inputs/Radio/index.jsx | 0 .../Components/Inputs/Search/index.jsx | 0 .../Components/Inputs/Select/index.css | 0 .../Components/Inputs/Select/index.jsx | 0 .../Inputs/TextInput/Adornments/index.jsx | 0 .../Components/Inputs/TextInput/index.jsx | 0 .../src => src}/Components/Label/index.css | 0 .../src => src}/Components/Label/index.jsx | 0 .../Components/LanguageSelector.jsx | 0 .../Components/Layouts/HomeLayout/index.css | 0 .../Components/Layouts/HomeLayout/index.jsx | 0 {Client/src => src}/Components/Link/index.jsx | 0 {Client/src => src}/Components/Link/link.css | 0 .../Components/MonitorCountHeader/index.jsx | 0 .../MonitorCountHeader/skeleton.jsx | 0 .../Components/MonitorCreateHeader/index.jsx | 0 .../MonitorCreateHeader/skeleton.jsx | 0 .../ConfigButton/index.jsx | 0 .../Components/MonitorStatusHeader/index.jsx | 0 .../MonitorStatusHeader/skeleton.jsx | 0 .../MonitorTimeFrameHeader/index.jsx | 0 .../MonitorTimeFrameHeader/skeleton.jsx | 0 .../Components/ProgressBars/index.css | 0 .../Components/ProgressBars/index.jsx | 0 .../Components/ProgressStepper/index.css | 0 .../Components/ProgressStepper/index.jsx | 0 .../ProtectedDistributedUptimeRoute/index.jsx | 0 .../Components/ProtectedRoute/index.jsx | 0 .../src => src}/Components/Sidebar/index.css | 0 .../src => src}/Components/Sidebar/index.jsx | 0 .../Components/Skeletons/FullPage/index.jsx | 0 .../src => src}/Components/StatBox/index.jsx | 0 .../Components/StatusBoxes/index.jsx | 0 .../Components/StatusBoxes/skeleton.jsx | 0 .../TabPanels/Account/PasswordPanel.jsx | 0 .../TabPanels/Account/ProfilePanel.jsx | 0 .../TabPanels/Account/TeamPanel.jsx | 0 .../Table/TablePagination/Actions/index.jsx | 0 .../Table/TablePagination/index.jsx | 0 .../src => src}/Components/Table/index.jsx | 0 .../src => src}/Components/Table/skeleton.jsx | 0 .../Components/ThemeSwitch/SunAndMoonIcon.jsx | 0 .../Components/ThemeSwitch/index.css | 0 .../Components/ThemeSwitch/index.jsx | 0 .../Components/WalletProvider/index.jsx | 0 .../src => src}/Features/Auth/authSlice.js | 0 .../infrastructureMonitorsSlice.js | 0 .../PageSpeedMonitor/pageSpeedMonitorSlice.js | 0 .../Features/Settings/settingsSlice.js | 0 {Client/src => src}/Features/UI/uiSlice.js | 0 .../UptimeMonitors/uptimeMonitorsSlice.js | 0 {Client/src => src}/Hooks/useIsAdmin.js | 0 {Client/src => src}/Hooks/useMonitorUtils.js | 0 {Client/src => src}/Pages/Account/index.css | 0 {Client/src => src}/Pages/Account/index.jsx | 0 {Client/src => src}/Pages/Auth/CheckEmail.jsx | 0 .../src => src}/Pages/Auth/ForgotPassword.jsx | 0 .../Pages/Auth/Login/Components/EmailStep.jsx | 0 .../Login/Components/ForgotPasswordLabel.jsx | 0 .../Auth/Login/Components/PasswordStep.jsx | 0 .../src => src}/Pages/Auth/Login/Login.jsx | 0 .../Pages/Auth/NewPasswordConfirmed.jsx | 0 .../Pages/Auth/Register/Register.jsx | 0 .../Pages/Auth/Register/StepOne/index.jsx | 0 .../Pages/Auth/Register/StepThree/index.jsx | 0 .../Pages/Auth/Register/StepTwo/index.jsx | 0 .../src => src}/Pages/Auth/SetNewPassword.jsx | 0 .../Pages/Auth/hooks/useValidatePassword.jsx | 0 {Client/src => src}/Pages/Auth/index.css | 0 .../useCreateDistributedUptimeMonitor.jsx | 0 .../Create/Hooks/useMonitorFetch.jsx | 0 .../Pages/DistributedUptime/Create/index.jsx | 0 .../Components/ControlsHeader/index.jsx | 0 .../Details/Components/DeviceTicker/index.jsx | 0 .../DistributedUptimeMapStyle.json | 0 .../DistributedUptimeMap/buildStyle.js | 0 .../Components/DistributedUptimeMap/index.jsx | 0 .../Area/index.jsx | 0 .../Bar/index.jsx | 0 .../Helpers/Tick.jsx | 0 .../Helpers/ToolTip.jsx | 0 .../DistributedUptimeResponseChart/index.jsx | 0 .../Details/Components/Footer/index.jsx | 0 .../Details/Components/LastUpdate/index.jsx | 0 .../Components/MonitorHeader/index.jsx | 0 .../Components/NextExpectedCheck/index.jsx | 0 .../Details/Components/Skeleton/index.jsx | 0 .../Details/Components/StatBoxes/index.jsx | 0 .../Details/Hooks/useDeleteMonitor.jsx | 0 .../Details/Hooks/useSubscribeToDetails.jsx | 0 .../Pages/DistributedUptime/Details/index.jsx | 0 .../Components/MonitorTable/index.jsx | 0 .../Monitors/Components/Skeleton/index.jsx | 0 .../Monitors/Hooks/useSubscribeToMonitors.jsx | 0 .../DistributedUptime/Monitors/index.jsx | 0 .../Components/VisuallyHiddenInput/index.jsx | 0 .../DistributedUptimeStatus/Create/index.jsx | 0 .../Status/Components/Skeleton/index.jsx | 0 .../Status/Hooks/useStatusPageFetchByUrl.jsx | 0 .../DistributedUptimeStatus/Status/index.jsx | 0 .../Components/IncidentTable/index.jsx | 0 .../Components/OptionsHeader/index.jsx | 0 .../Components/OptionsHeader/skeleton.jsx | 0 .../Pages/Incidents/Hooks/useChecksFetch.jsx | 0 .../Incidents/Hooks/useMonitorsFetch.jsx | 0 {Client/src => src}/Pages/Incidents/index.jsx | 0 .../Components/CustomThreshold/index.jsx | 0 .../Pages/Infrastructure/Create/index.jsx | 0 .../AreaChartBoxes/InfraAreaChart.jsx | 0 .../Components/AreaChartBoxes/index.jsx | 0 .../Components/AreaChartBoxes/skeleton.jsx | 0 .../Components/BaseContainer/index.jsx | 0 .../Details/Components/GaugeBoxes/Gauge.jsx | 0 .../Details/Components/GaugeBoxes/index.jsx | 0 .../Components/GaugeBoxes/skeleton.jsx | 0 .../Details/Components/StatusBoxes/index.jsx | 0 .../Hooks/useHardwareMonitorsFetch.jsx | 0 .../Details/Hooks/useHardwareUtils.jsx | 0 .../Pages/Infrastructure/Details/index.jsx | 0 .../Components/MonitorsTable/index.jsx | 0 .../Components/MonitorsTableMenu/index.jsx | 0 .../Monitors/Hooks/useMonitorFetch.jsx | 0 .../Pages/Infrastructure/Monitors/index.jsx | 0 .../src => src}/Pages/Integrations/index.css | 0 .../src => src}/Pages/Integrations/index.jsx | 0 .../Maintenance/CreateMaintenance/index.css | 0 .../Maintenance/CreateMaintenance/index.jsx | 0 .../MaintenanceTable/ActionsMenu/index.jsx | 0 .../Maintenance/MaintenanceTable/index.jsx | 0 .../src => src}/Pages/Maintenance/index.css | 0 .../src => src}/Pages/Maintenance/index.jsx | 0 {Client/src => src}/Pages/NotFound/index.jsx | 0 .../Pages/PageSpeed/Configure/index.css | 0 .../Pages/PageSpeed/Configure/index.jsx | 0 .../Pages/PageSpeed/Configure/skeleton.jsx | 0 .../Pages/PageSpeed/Create/index.css | 0 .../Pages/PageSpeed/Create/index.jsx | 0 .../Details/Components/Charts/AreaChart.jsx | 0 .../Components/Charts/AreaChartLegend.jsx | 0 .../Details/Components/Charts/PieChart.jsx | 0 .../Components/Charts/PieChartLegend.jsx | 0 .../Components/PageSpeedAreaChart/index.jsx | 0 .../PageSpeedAreaChart/skeleton.jsx | 0 .../Components/PageSpeedStatusBoxes/index.jsx | 0 .../Components/PerformanceReport/index.jsx | 0 .../Components/PerformanceReport/skeleton.jsx | 0 .../Details/Hooks/useMonitorFetch.jsx | 0 .../Pages/PageSpeed/Details/index.jsx | 0 .../Monitors/Components/Card/index.jsx | 0 .../Monitors/Components/MonitorGrid/index.jsx | 0 .../Monitors/Hooks/useMonitorsFetch.jsx | 0 .../Pages/PageSpeed/Monitors/index.jsx | 0 {Client/src => src}/Pages/Settings/index.jsx | 0 .../Create/Components/MonitorList/index.jsx | 0 .../Create/Components/Progress/index.jsx | 0 .../Create/Components/Skeleton/index.jsx | 0 .../Create/Components/Tabs/Content.jsx | 0 .../Create/Components/Tabs/Settings.jsx | 0 .../Create/Components/Tabs/index.jsx | 0 .../Create/Hooks/useCreateStatusPage.jsx | 0 .../Create/Hooks/useMonitorsFetch.jsx | 0 .../Pages/StatusPage/Create/index.jsx | 0 .../Status/Components/AdminLink/index.jsx | 0 .../Components/ControlsHeader/index.jsx | 0 .../Status/Components/MonitorsList/index.jsx | 0 .../Status/Components/Skeleton/index.jsx | 0 .../Status/Components/StatusBar/index.jsx | 0 .../Status/Hooks/useStatusPageDelete.jsx | 0 .../Status/Hooks/useStatusPageFetch.jsx | 0 .../Pages/StatusPage/Status/index.jsx | 0 .../Components/StatusPagesTable/index.jsx | 0 .../StatusPages/Hooks/useStatusPagesFetch.jsx | 0 .../Pages/StatusPage/StatusPages/index.jsx | 0 .../Pages/Uptime/Configure/index.css | 0 .../Pages/Uptime/Configure/index.jsx | 0 .../Pages/Uptime/Configure/skeleton.jsx | 0 .../src => src}/Pages/Uptime/Create/index.css | 0 .../src => src}/Pages/Uptime/Create/index.jsx | 0 .../Details/Components/ChartBoxes/index.jsx | 0 .../Components/ChartBoxes/skeleton.jsx | 0 .../Components/Charts/CustomLabels.jsx | 0 .../Components/Charts/DownBarChart.jsx | 0 .../Components/Charts/ResponseGaugeChart.jsx | 0 .../Components/Charts/ResponseTimeChart.jsx | 0 .../Charts/ResponseTimeChartSkeleton.jsx | 0 .../Details/Components/Charts/UpBarChart.jsx | 0 .../Components/ResponseTable/index.jsx | 0 .../Components/ResponseTable/skeleton.jsx | 0 .../Components/UptimeStatusBoxes/index.jsx | 0 .../Details/Hooks/useCertificateFetch.jsx | 0 .../Uptime/Details/Hooks/useChecksFetch.jsx | 0 .../Uptime/Details/Hooks/useMonitorFetch.jsx | 0 .../Pages/Uptime/Details/index.jsx | 0 .../Components/LoadingSpinner/index.jsx | 0 .../Components/SearchComponent/index.jsx | 0 .../Monitors/Components/Skeleton/index.jsx | 0 .../Monitors/Components/StatusBoxes/index.jsx | 0 .../Components/StatusBoxes/skeleton.jsx | 0 .../Components/StatusBoxes/statusBox.jsx | 0 .../Components/UptimeDataTable/index.jsx | 0 .../Uptime/Monitors/Hooks/useDebounce.jsx | 0 .../Monitors/Hooks/useMonitorsFetch.jsx | 0 .../Pages/Uptime/Monitors/Hooks/useUtils.jsx | 0 .../Pages/Uptime/Monitors/index.jsx | 0 {Client/src => src}/Routes/index.jsx | 0 {Client/src => src}/Utils/Logger.js | 0 {Client/src => src}/Utils/NetworkService.js | 0 .../Utils/NetworkServiceProvider.jsx | 0 {Client/src => src}/Utils/ReadMe.md | 0 {Client/src => src}/Utils/Theme/constants.js | 0 {Client/src => src}/Utils/Theme/darkTheme.js | 0 .../Utils/Theme/extractColorObject.js | 0 .../src => src}/Utils/Theme/globalTheme.js | 0 {Client/src => src}/Utils/Theme/lightTheme.js | 0 {Client/src => src}/Utils/debounce.jsx | 0 {Client/src => src}/Utils/fileUtils.js | 0 {Client/src => src}/Utils/greeting.jsx | 0 {Client/src => src}/Utils/i18n.js | 0 {Client/src => src}/Utils/monitorUtils.js | 0 {Client/src => src}/Utils/stringUtils.js | 0 {Client/src => src}/Utils/timeUtils.js | 0 {Client/src => src}/Utils/timezones.json | 0 {Client/src => src}/Utils/toastUtils.jsx | 0 {Client/src => src}/Validation/error.js | 0 {Client/src => src}/Validation/validation.js | 0 {Client/src => src}/assets/Images/Google.png | Bin .../assets/Images/avatar_placeholder.png | Bin .../assets/Images/background-grid.svg | 0 .../src => src}/assets/Images/bwl-logo.svg | 0 .../assets/Images/create-placeholder-dark.svg | 0 .../assets/Images/create-placeholder.svg | 0 .../assets/Images/data_placeholder.svg | 0 .../assets/Images/data_placeholder_dark.svg | 0 .../assets/Images/logo_placeholder.svg | 0 .../src => src}/assets/Images/sushi_404.svg | 0 .../assets/icons/average-response-icon.svg | 0 .../assets/icons/calendar-check.svg | 0 {Client/src => src}/assets/icons/calendar.svg | 0 .../src => src}/assets/icons/certificate.svg | 0 .../src => src}/assets/icons/changeLog.svg | 0 .../assets/icons/check-outlined.svg | 0 {Client/src => src}/assets/icons/check.svg | 0 .../assets/icons/checkbox-filled.svg | 0 .../assets/icons/checkbox-green.svg | 0 .../assets/icons/checkbox-outline.svg | 0 .../src => src}/assets/icons/checkbox-red.svg | 0 .../assets/icons/checkmate-icon.svg | 0 .../src => src}/assets/icons/clock-snooze.svg | 0 {Client/src => src}/assets/icons/cpu-chip.svg | 0 .../src => src}/assets/icons/dashboard.svg | 0 .../src => src}/assets/icons/discord-icon.svg | 0 .../assets/icons/distributed-uptime.svg | 0 {Client/src => src}/assets/icons/docs.svg | 0 .../assets/icons/dots-vertical.svg | 0 .../src => src}/assets/icons/down-arrow.svg | 0 {Client/src => src}/assets/icons/edit.svg | 0 {Client/src => src}/assets/icons/email.svg | 0 {Client/src => src}/assets/icons/folder.svg | 0 .../src => src}/assets/icons/history-icon.svg | 0 .../src => src}/assets/icons/incidents.svg | 0 .../src => src}/assets/icons/integrations.svg | 0 .../assets/icons/interval-check.svg | 0 {Client/src => src}/assets/icons/key.svg | 0 .../assets/icons/left-arrow-double.svg | 0 .../assets/icons/left-arrow-long.svg | 0 .../src => src}/assets/icons/left-arrow.svg | 0 {Client/src => src}/assets/icons/lock.svg | 0 {Client/src => src}/assets/icons/logout.svg | 0 {Client/src => src}/assets/icons/mail.svg | 0 .../src => src}/assets/icons/maintenance.svg | 0 .../assets/icons/monitor-graph-line.svg | 0 {Client/src => src}/assets/icons/monitors.svg | 0 .../assets/icons/open-in-new-page.svg | 0 .../src => src}/assets/icons/page-speed.svg | 0 .../src => src}/assets/icons/pause-icon.svg | 0 .../assets/icons/performance-report.svg | 0 .../assets/icons/radio-checked.svg | 0 .../assets/icons/response-time-icon.svg | 0 .../src => src}/assets/icons/resume-icon.svg | 0 .../assets/icons/right-arrow-double.svg | 0 .../src => src}/assets/icons/right-arrow.svg | 0 .../src => src}/assets/icons/ruler-icon.svg | 0 {Client/src => src}/assets/icons/search.svg | 0 .../assets/icons/selector-vertical.svg | 0 .../assets/icons/settings-bold.svg | 0 {Client/src => src}/assets/icons/settings.svg | 0 .../src => src}/assets/icons/slack-icon.svg | 0 .../src => src}/assets/icons/solana_logo.svg | 0 .../assets/icons/speedometer-icon.svg | 0 .../src => src}/assets/icons/status-pages.svg | 0 {Client/src => src}/assets/icons/support.svg | 0 .../assets/icons/top-right-arrow.svg | 0 .../src => src}/assets/icons/trash-bin.svg | 0 {Client/src => src}/assets/icons/up-arrow.svg | 0 {Client/src => src}/assets/icons/upt_logo.png | Bin .../src => src}/assets/icons/uptime-icon.svg | 0 .../src => src}/assets/icons/user-edit.svg | 0 {Client/src => src}/assets/icons/user-two.svg | 0 {Client/src => src}/assets/icons/user.svg | 0 .../src => src}/assets/icons/zapier-icon.svg | 0 {Client/src => src}/index.css | 0 {Client/src => src}/locales/gb.json | 0 {Client/src => src}/locales/tr.json | 0 {Client/src => src}/main.jsx | 0 {Client/src => src}/store.js | 0 Client/vite.config.js => vite.config.js | 0 531 files changed, 49 insertions(+), 34690 deletions(-) rename Client/.env.production => .env.production (100%) rename Client/.eslintrc.cjs => .eslintrc.cjs (100%) delete mode 100644 Client/.gitignore delete mode 100644 Client/README.md delete mode 100644 Client/index.html delete mode 100644 Docker/.gitignore delete mode 100755 Docker/dev/build_images.sh delete mode 100644 Docker/dev/client.Dockerfile delete mode 100644 Docker/dev/docker-compose.yaml delete mode 100644 Docker/dev/mongoDB.Dockerfile delete mode 100644 Docker/dev/redis.Dockerfile delete mode 100644 Docker/dev/server.Dockerfile delete mode 100755 Docker/dist/build_images.sh delete mode 100644 Docker/dist/client.Dockerfile delete mode 100644 Docker/dist/docker-compose.yaml delete mode 100644 Docker/dist/mongoDB.Dockerfile delete mode 100644 Docker/dist/nginx/conf.d/default.conf delete mode 100644 Docker/dist/redis.Dockerfile delete mode 100644 Docker/dist/server.Dockerfile delete mode 100755 Docker/prod/build_images.sh delete mode 100644 Docker/prod/certbot-compose.yaml delete mode 100644 Docker/prod/client.Dockerfile delete mode 100644 Docker/prod/docker-compose.yaml delete mode 100644 Docker/prod/mongo/init/create_users.js delete mode 100644 Docker/prod/mongoDB.Dockerfile delete mode 100644 Docker/prod/nginx/conf.d/cerbot delete mode 100644 Docker/prod/nginx/conf.d/default.conf delete mode 100644 Docker/prod/redis.Dockerfile delete mode 100644 Docker/prod/server.Dockerfile delete mode 100755 Docker/test/build_images.sh delete mode 100644 Docker/test/cerbot-compose.yaml delete mode 100644 Docker/test/client.Dockerfile delete mode 100644 Docker/test/docker-compose.yaml delete mode 100644 Docker/test/mongo/init/create_users.js delete mode 100644 Docker/test/mongoDB.Dockerfile delete mode 100644 Docker/test/nginx/conf.d/certbot delete mode 100644 Docker/test/nginx/conf.d/default.conf delete mode 100644 Docker/test/redis.Dockerfile delete mode 100644 Docker/test/server.Dockerfile delete mode 100644 Server/.dockerignore delete mode 100644 Server/.gitignore delete mode 100644 Server/.mocharc.cjs delete mode 100644 Server/.nycrc delete mode 100644 Server/controllers/authController.js delete mode 100644 Server/controllers/checkController.js delete mode 100644 Server/controllers/controllerUtils.js delete mode 100644 Server/controllers/distributedUptimeController.js delete mode 100644 Server/controllers/inviteController.js delete mode 100644 Server/controllers/maintenanceWindowController.js delete mode 100644 Server/controllers/monitorController.js delete mode 100644 Server/controllers/notificationController.js delete mode 100644 Server/controllers/queueController.js delete mode 100644 Server/controllers/settingsController.js delete mode 100644 Server/controllers/statusPageController.js delete mode 100644 Server/db/FakeDb.js delete mode 100644 Server/db/models/AppSettings.js delete mode 100644 Server/db/models/Check.js delete mode 100644 Server/db/models/DistributedUptimeCheck.js delete mode 100644 Server/db/models/HardwareCheck.js delete mode 100644 Server/db/models/InviteToken.js delete mode 100644 Server/db/models/MaintenanceWindow.js delete mode 100644 Server/db/models/Monitor.js delete mode 100644 Server/db/models/Notification.js delete mode 100644 Server/db/models/PageSpeedCheck.js delete mode 100644 Server/db/models/RecoveryToken.js delete mode 100644 Server/db/models/StatusPage.js delete mode 100644 Server/db/models/Team.js delete mode 100644 Server/db/models/User.js delete mode 100644 Server/db/mongo/MongoDB.js delete mode 100644 Server/db/mongo/modules/checkModule.js delete mode 100644 Server/db/mongo/modules/distributedCheckModule.js delete mode 100644 Server/db/mongo/modules/hardwareCheckModule.js delete mode 100644 Server/db/mongo/modules/inviteModule.js delete mode 100644 Server/db/mongo/modules/maintenanceWindowModule.js delete mode 100644 Server/db/mongo/modules/monitorModule.js delete mode 100644 Server/db/mongo/modules/monitorModuleQueries.js delete mode 100644 Server/db/mongo/modules/notificationModule.js delete mode 100644 Server/db/mongo/modules/pageSpeedCheckModule.js delete mode 100644 Server/db/mongo/modules/recoveryModule.js delete mode 100644 Server/db/mongo/modules/settingsModule.js delete mode 100644 Server/db/mongo/modules/statusPageModule.js delete mode 100644 Server/db/mongo/modules/userModule.js delete mode 100644 Server/db/mongo/utils/seedDb.js delete mode 100644 Server/eslint.config.js delete mode 100644 Server/index.js delete mode 100644 Server/locales/en.json delete mode 100644 Server/locales/en.json.bak delete mode 100644 Server/middleware/handleErrors.js delete mode 100644 Server/middleware/isAllowed.js delete mode 100644 Server/middleware/languageMiddleware.js delete mode 100644 Server/middleware/responseHandler.js delete mode 100644 Server/middleware/verifyJWT.js delete mode 100644 Server/middleware/verifyOwnership.js delete mode 100644 Server/middleware/verifySuperAdmin.js delete mode 100644 Server/nodemon.json delete mode 100644 Server/openapi.json delete mode 100644 Server/package-lock.json delete mode 100644 Server/package.json delete mode 100644 Server/routes/authRoute.js delete mode 100644 Server/routes/checkRoute.js delete mode 100644 Server/routes/distributedUptimeRoute.js delete mode 100644 Server/routes/inviteRoute.js delete mode 100644 Server/routes/maintenanceWindowRoute.js delete mode 100644 Server/routes/monitorRoute.js delete mode 100644 Server/routes/notificationRoute.js delete mode 100644 Server/routes/queueRoute.js delete mode 100644 Server/routes/settingsRoute.js delete mode 100644 Server/routes/statusPageRoute.js delete mode 100644 Server/service/emailService.js delete mode 100644 Server/service/jobQueue.js delete mode 100644 Server/service/networkService.js delete mode 100644 Server/service/notificationService.js delete mode 100644 Server/service/serviceRegistry.js delete mode 100644 Server/service/settingsService.js delete mode 100644 Server/service/statusService.js delete mode 100644 Server/service/stringService.js delete mode 100644 Server/service/translationService.js delete mode 100644 Server/templates/addReview.mjml delete mode 100644 Server/templates/employeeActivation.mjml delete mode 100644 Server/templates/hardwareIncident.mjml delete mode 100644 Server/templates/noIncidentsThisWeek.mjml delete mode 100644 Server/templates/passwordReset.mjml delete mode 100644 Server/templates/serverIsDown.mjml delete mode 100644 Server/templates/serverIsUp.mjml delete mode 100644 Server/templates/welcomeEmail.mjml delete mode 100644 Server/tests/controllers/authController.test.js delete mode 100644 Server/tests/controllers/checkController.test.js delete mode 100644 Server/tests/controllers/controllerUtils.test.js delete mode 100644 Server/tests/controllers/inviteController.test.js delete mode 100644 Server/tests/controllers/maintenanceWindowController.test.js delete mode 100644 Server/tests/controllers/monitorController.test.js delete mode 100644 Server/tests/controllers/queueController.test.js delete mode 100644 Server/tests/controllers/settingsController.test.js delete mode 100644 Server/tests/controllers/statusPageController.test.js delete mode 100644 Server/tests/db/checkModule.test.js delete mode 100644 Server/tests/db/hardwareCheckModule.test.js delete mode 100644 Server/tests/db/inviteModule.test.js delete mode 100644 Server/tests/db/maintenanceWindowModule.test.js delete mode 100644 Server/tests/db/monitorModule.test.js delete mode 100644 Server/tests/db/notificationModule.test.js delete mode 100644 Server/tests/db/pageSpeedCheckModule.test.js delete mode 100644 Server/tests/db/recoveryModule.test.js delete mode 100644 Server/tests/db/settingsModule.test.js delete mode 100644 Server/tests/db/statusPageModule.test.js delete mode 100644 Server/tests/db/userModule.test.js delete mode 100644 Server/tests/services/emailService.test.js delete mode 100644 Server/tests/services/jobQueue.test.js delete mode 100644 Server/tests/services/networkService.test.js delete mode 100644 Server/tests/services/notificationService.test.js delete mode 100644 Server/tests/services/settingsService.test.js delete mode 100644 Server/tests/services/statusService.test.js delete mode 100644 Server/tests/utils/dataUtils.test.js delete mode 100644 Server/tests/utils/imageProcessing.test.js delete mode 100644 Server/tests/utils/logger.test.js delete mode 100644 Server/tests/utils/messages.test.js delete mode 100644 Server/tests/utils/utils.test.js delete mode 100644 Server/utils/dataUtils.js delete mode 100644 Server/utils/demoMonitors.json delete mode 100644 Server/utils/demoMonitorsOld.json delete mode 100644 Server/utils/imageProcessing.js delete mode 100644 Server/utils/logger.js delete mode 100644 Server/utils/utils.js delete mode 100644 Server/validation/joi.js delete mode 100644 docs/diagrams/Bluewave Uptime Auth Flow.pdf delete mode 100644 docs/diagrams/Bluewave Uptime Auth Flow.png delete mode 100644 docs/diagrams/Bluewave Uptime High Level Overview.pdf delete mode 100644 docs/diagrams/Bluewave Uptime High Level Overview.png delete mode 100644 docs/diagrams/Bluewave Uptime JobQueue Flow.pdf delete mode 100644 docs/diagrams/Bluewave Uptime JobQueue Flow.png delete mode 100644 docs/diagrams/Bluewave Uptime Monitor Flow.pdf delete mode 100644 docs/diagrams/Bluewave Uptime Monitor Flow.png rename Client/env.sh => env.sh (100%) delete mode 100644 guides/.gitbook/assets/Screenshot 2024-10-10 at 10.37.36 PM.png delete mode 100644 guides/users-guide/maintenance-mode.md rename Client/package-lock.json => package-lock.json (100%) rename Client/package.json => package.json (100%) rename {Client/public => public}/checkmate_favicon.svg (100%) rename {Client/src => src}/App.jsx (100%) rename {Client/src => src}/Components/ActionsMenu/index.jsx (100%) rename {Client/src => src}/Components/Alert/index.css (100%) rename {Client/src => src}/Components/Alert/index.jsx (100%) rename {Client/src => src}/Components/Animated/PulseDot.jsx (100%) rename {Client/src => src}/Components/Avatar/index.css (100%) rename {Client/src => src}/Components/Avatar/index.jsx (100%) rename {Client/src => src}/Components/Breadcrumbs/index.css (100%) rename {Client/src => src}/Components/Breadcrumbs/index.jsx (100%) rename {Client/src => src}/Components/Charts/AreaChart/index.jsx (100%) rename {Client/src => src}/Components/Charts/BarChart/index.css (100%) rename {Client/src => src}/Components/Charts/BarChart/index.jsx (100%) rename {Client/src => src}/Components/Charts/ChartBox/index.jsx (100%) rename {Client/src => src}/Components/Charts/CustomGauge/index.css (100%) rename {Client/src => src}/Components/Charts/CustomGauge/index.jsx (100%) rename {Client/src => src}/Components/Charts/LegendBox/index.jsx (100%) rename {Client/src => src}/Components/Charts/MonitorDetailsAreaChart/index.css (100%) rename {Client/src => src}/Components/Charts/MonitorDetailsAreaChart/index.jsx (100%) rename {Client/src => src}/Components/Charts/StatusPageBarChart/index.jsx (100%) rename {Client/src => src}/Components/Charts/Utils/chartUtilFunctions.js (100%) rename {Client/src => src}/Components/Charts/Utils/chartUtils.jsx (100%) rename {Client/src => src}/Components/Charts/Utils/gradientUtils.jsx (100%) rename {Client/src => src}/Components/Check/Check.jsx (100%) rename {Client/src => src}/Components/Check/check.css (100%) rename {Client/src => src}/Components/CircularCount/index.jsx (100%) rename {Client/src => src}/Components/ConfigBox/index.jsx (100%) rename {Client/src => src}/Components/Dialog/genericDialog.jsx (100%) rename {Client/src => src}/Components/Dialog/index.jsx (100%) rename {Client/src => src}/Components/Dot/index.jsx (100%) rename {Client/src => src}/Components/Fallback/index.css (100%) rename {Client/src => src}/Components/Fallback/index.jsx (100%) rename {Client/src => src}/Components/GenericFallback/NetworkError.jsx (100%) rename {Client/src => src}/Components/GenericFallback/index.jsx (100%) rename {Client/src => src}/Components/HOC/withAdminCheck.jsx (100%) rename {Client/src => src}/Components/Heading/index.jsx (100%) rename {Client/src => src}/Components/Host/index.jsx (100%) rename {Client/src => src}/Components/HttpStatusLabel/index.jsx (100%) rename {Client/src => src}/Components/IconBox/index.jsx (100%) rename {Client/src => src}/Components/Image/index.jsx (100%) rename {Client/src => src}/Components/Inputs/Checkbox/index.css (100%) rename {Client/src => src}/Components/Inputs/Checkbox/index.jsx (100%) rename {Client/src => src}/Components/Inputs/ColorPicker/index.jsx (100%) rename {Client/src => src}/Components/Inputs/Image/index.css (100%) rename {Client/src => src}/Components/Inputs/Image/index.jsx (100%) rename {Client/src => src}/Components/Inputs/Radio/index.css (100%) rename {Client/src => src}/Components/Inputs/Radio/index.jsx (100%) rename {Client/src => src}/Components/Inputs/Search/index.jsx (100%) rename {Client/src => src}/Components/Inputs/Select/index.css (100%) rename {Client/src => src}/Components/Inputs/Select/index.jsx (100%) rename {Client/src => src}/Components/Inputs/TextInput/Adornments/index.jsx (100%) rename {Client/src => src}/Components/Inputs/TextInput/index.jsx (100%) rename {Client/src => src}/Components/Label/index.css (100%) rename {Client/src => src}/Components/Label/index.jsx (100%) rename {Client/src => src}/Components/LanguageSelector.jsx (100%) rename {Client/src => src}/Components/Layouts/HomeLayout/index.css (100%) rename {Client/src => src}/Components/Layouts/HomeLayout/index.jsx (100%) rename {Client/src => src}/Components/Link/index.jsx (100%) rename {Client/src => src}/Components/Link/link.css (100%) rename {Client/src => src}/Components/MonitorCountHeader/index.jsx (100%) rename {Client/src => src}/Components/MonitorCountHeader/skeleton.jsx (100%) rename {Client/src => src}/Components/MonitorCreateHeader/index.jsx (100%) rename {Client/src => src}/Components/MonitorCreateHeader/skeleton.jsx (100%) rename {Client/src => src}/Components/MonitorStatusHeader/ConfigButton/index.jsx (100%) rename {Client/src => src}/Components/MonitorStatusHeader/index.jsx (100%) rename {Client/src => src}/Components/MonitorStatusHeader/skeleton.jsx (100%) rename {Client/src => src}/Components/MonitorTimeFrameHeader/index.jsx (100%) rename {Client/src => src}/Components/MonitorTimeFrameHeader/skeleton.jsx (100%) rename {Client/src => src}/Components/ProgressBars/index.css (100%) rename {Client/src => src}/Components/ProgressBars/index.jsx (100%) rename {Client/src => src}/Components/ProgressStepper/index.css (100%) rename {Client/src => src}/Components/ProgressStepper/index.jsx (100%) rename {Client/src => src}/Components/ProtectedDistributedUptimeRoute/index.jsx (100%) rename {Client/src => src}/Components/ProtectedRoute/index.jsx (100%) rename {Client/src => src}/Components/Sidebar/index.css (100%) rename {Client/src => src}/Components/Sidebar/index.jsx (100%) rename {Client/src => src}/Components/Skeletons/FullPage/index.jsx (100%) rename {Client/src => src}/Components/StatBox/index.jsx (100%) rename {Client/src => src}/Components/StatusBoxes/index.jsx (100%) rename {Client/src => src}/Components/StatusBoxes/skeleton.jsx (100%) rename {Client/src => src}/Components/TabPanels/Account/PasswordPanel.jsx (100%) rename {Client/src => src}/Components/TabPanels/Account/ProfilePanel.jsx (100%) rename {Client/src => src}/Components/TabPanels/Account/TeamPanel.jsx (100%) rename {Client/src => src}/Components/Table/TablePagination/Actions/index.jsx (100%) rename {Client/src => src}/Components/Table/TablePagination/index.jsx (100%) rename {Client/src => src}/Components/Table/index.jsx (100%) rename {Client/src => src}/Components/Table/skeleton.jsx (100%) rename {Client/src => src}/Components/ThemeSwitch/SunAndMoonIcon.jsx (100%) rename {Client/src => src}/Components/ThemeSwitch/index.css (100%) rename {Client/src => src}/Components/ThemeSwitch/index.jsx (100%) rename {Client/src => src}/Components/WalletProvider/index.jsx (100%) rename {Client/src => src}/Features/Auth/authSlice.js (100%) rename {Client/src => src}/Features/InfrastructureMonitors/infrastructureMonitorsSlice.js (100%) rename {Client/src => src}/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js (100%) rename {Client/src => src}/Features/Settings/settingsSlice.js (100%) rename {Client/src => src}/Features/UI/uiSlice.js (100%) rename {Client/src => src}/Features/UptimeMonitors/uptimeMonitorsSlice.js (100%) rename {Client/src => src}/Hooks/useIsAdmin.js (100%) rename {Client/src => src}/Hooks/useMonitorUtils.js (100%) rename {Client/src => src}/Pages/Account/index.css (100%) rename {Client/src => src}/Pages/Account/index.jsx (100%) rename {Client/src => src}/Pages/Auth/CheckEmail.jsx (100%) rename {Client/src => src}/Pages/Auth/ForgotPassword.jsx (100%) rename {Client/src => src}/Pages/Auth/Login/Components/EmailStep.jsx (100%) rename {Client/src => src}/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx (100%) rename {Client/src => src}/Pages/Auth/Login/Components/PasswordStep.jsx (100%) rename {Client/src => src}/Pages/Auth/Login/Login.jsx (100%) rename {Client/src => src}/Pages/Auth/NewPasswordConfirmed.jsx (100%) rename {Client/src => src}/Pages/Auth/Register/Register.jsx (100%) rename {Client/src => src}/Pages/Auth/Register/StepOne/index.jsx (100%) rename {Client/src => src}/Pages/Auth/Register/StepThree/index.jsx (100%) rename {Client/src => src}/Pages/Auth/Register/StepTwo/index.jsx (100%) rename {Client/src => src}/Pages/Auth/SetNewPassword.jsx (100%) rename {Client/src => src}/Pages/Auth/hooks/useValidatePassword.jsx (100%) rename {Client/src => src}/Pages/Auth/index.css (100%) rename {Client/src => src}/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Create/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/ControlsHeader/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/DistributedUptimeMapStyle.json (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/buildStyle.js (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Area/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Bar/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/Tick.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/ToolTip.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/Footer/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/LastUpdate/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/NextExpectedCheck/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Details/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx (100%) rename {Client/src => src}/Pages/DistributedUptime/Monitors/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptimeStatus/Create/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx (100%) rename {Client/src => src}/Pages/DistributedUptimeStatus/Status/index.jsx (100%) rename {Client/src => src}/Pages/Incidents/Components/IncidentTable/index.jsx (100%) rename {Client/src => src}/Pages/Incidents/Components/OptionsHeader/index.jsx (100%) rename {Client/src => src}/Pages/Incidents/Components/OptionsHeader/skeleton.jsx (100%) rename {Client/src => src}/Pages/Incidents/Hooks/useChecksFetch.jsx (100%) rename {Client/src => src}/Pages/Incidents/Hooks/useMonitorsFetch.jsx (100%) rename {Client/src => src}/Pages/Incidents/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Create/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Details/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx (100%) rename {Client/src => src}/Pages/Infrastructure/Monitors/index.jsx (100%) rename {Client/src => src}/Pages/Integrations/index.css (100%) rename {Client/src => src}/Pages/Integrations/index.jsx (100%) rename {Client/src => src}/Pages/Maintenance/CreateMaintenance/index.css (100%) rename {Client/src => src}/Pages/Maintenance/CreateMaintenance/index.jsx (100%) rename {Client/src => src}/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx (100%) rename {Client/src => src}/Pages/Maintenance/MaintenanceTable/index.jsx (100%) rename {Client/src => src}/Pages/Maintenance/index.css (100%) rename {Client/src => src}/Pages/Maintenance/index.jsx (100%) rename {Client/src => src}/Pages/NotFound/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Configure/index.css (100%) rename {Client/src => src}/Pages/PageSpeed/Configure/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Configure/skeleton.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Create/index.css (100%) rename {Client/src => src}/Pages/PageSpeed/Create/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Details/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Monitors/Components/Card/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx (100%) rename {Client/src => src}/Pages/PageSpeed/Monitors/index.jsx (100%) rename {Client/src => src}/Pages/Settings/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/MonitorList/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/Progress/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/Tabs/Content.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/Tabs/Settings.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Components/Tabs/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Create/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Components/AdminLink/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Components/MonitorsList/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Components/StatusBar/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx (100%) rename {Client/src => src}/Pages/StatusPage/Status/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx (100%) rename {Client/src => src}/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx (100%) rename {Client/src => src}/Pages/StatusPage/StatusPages/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Configure/index.css (100%) rename {Client/src => src}/Pages/Uptime/Configure/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Configure/skeleton.jsx (100%) rename {Client/src => src}/Pages/Uptime/Create/index.css (100%) rename {Client/src => src}/Pages/Uptime/Create/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/ChartBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/ResponseTable/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Hooks/useChecksFetch.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx (100%) rename {Client/src => src}/Pages/Uptime/Details/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/Skeleton/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Hooks/useDebounce.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/Hooks/useUtils.jsx (100%) rename {Client/src => src}/Pages/Uptime/Monitors/index.jsx (100%) rename {Client/src => src}/Routes/index.jsx (100%) rename {Client/src => src}/Utils/Logger.js (100%) rename {Client/src => src}/Utils/NetworkService.js (100%) rename {Client/src => src}/Utils/NetworkServiceProvider.jsx (100%) rename {Client/src => src}/Utils/ReadMe.md (100%) rename {Client/src => src}/Utils/Theme/constants.js (100%) rename {Client/src => src}/Utils/Theme/darkTheme.js (100%) rename {Client/src => src}/Utils/Theme/extractColorObject.js (100%) rename {Client/src => src}/Utils/Theme/globalTheme.js (100%) rename {Client/src => src}/Utils/Theme/lightTheme.js (100%) rename {Client/src => src}/Utils/debounce.jsx (100%) rename {Client/src => src}/Utils/fileUtils.js (100%) rename {Client/src => src}/Utils/greeting.jsx (100%) rename {Client/src => src}/Utils/i18n.js (100%) rename {Client/src => src}/Utils/monitorUtils.js (100%) rename {Client/src => src}/Utils/stringUtils.js (100%) rename {Client/src => src}/Utils/timeUtils.js (100%) rename {Client/src => src}/Utils/timezones.json (100%) rename {Client/src => src}/Utils/toastUtils.jsx (100%) rename {Client/src => src}/Validation/error.js (100%) rename {Client/src => src}/Validation/validation.js (100%) rename {Client/src => src}/assets/Images/Google.png (100%) rename {Client/src => src}/assets/Images/avatar_placeholder.png (100%) rename {Client/src => src}/assets/Images/background-grid.svg (100%) rename {Client/src => src}/assets/Images/bwl-logo.svg (100%) rename {Client/src => src}/assets/Images/create-placeholder-dark.svg (100%) rename {Client/src => src}/assets/Images/create-placeholder.svg (100%) rename {Client/src => src}/assets/Images/data_placeholder.svg (100%) rename {Client/src => src}/assets/Images/data_placeholder_dark.svg (100%) rename {Client/src => src}/assets/Images/logo_placeholder.svg (100%) rename {Client/src => src}/assets/Images/sushi_404.svg (100%) rename {Client/src => src}/assets/icons/average-response-icon.svg (100%) rename {Client/src => src}/assets/icons/calendar-check.svg (100%) rename {Client/src => src}/assets/icons/calendar.svg (100%) rename {Client/src => src}/assets/icons/certificate.svg (100%) rename {Client/src => src}/assets/icons/changeLog.svg (100%) rename {Client/src => src}/assets/icons/check-outlined.svg (100%) rename {Client/src => src}/assets/icons/check.svg (100%) rename {Client/src => src}/assets/icons/checkbox-filled.svg (100%) rename {Client/src => src}/assets/icons/checkbox-green.svg (100%) rename {Client/src => src}/assets/icons/checkbox-outline.svg (100%) rename {Client/src => src}/assets/icons/checkbox-red.svg (100%) rename {Client/src => src}/assets/icons/checkmate-icon.svg (100%) rename {Client/src => src}/assets/icons/clock-snooze.svg (100%) rename {Client/src => src}/assets/icons/cpu-chip.svg (100%) rename {Client/src => src}/assets/icons/dashboard.svg (100%) rename {Client/src => src}/assets/icons/discord-icon.svg (100%) rename {Client/src => src}/assets/icons/distributed-uptime.svg (100%) rename {Client/src => src}/assets/icons/docs.svg (100%) rename {Client/src => src}/assets/icons/dots-vertical.svg (100%) rename {Client/src => src}/assets/icons/down-arrow.svg (100%) rename {Client/src => src}/assets/icons/edit.svg (100%) rename {Client/src => src}/assets/icons/email.svg (100%) rename {Client/src => src}/assets/icons/folder.svg (100%) rename {Client/src => src}/assets/icons/history-icon.svg (100%) rename {Client/src => src}/assets/icons/incidents.svg (100%) rename {Client/src => src}/assets/icons/integrations.svg (100%) rename {Client/src => src}/assets/icons/interval-check.svg (100%) rename {Client/src => src}/assets/icons/key.svg (100%) rename {Client/src => src}/assets/icons/left-arrow-double.svg (100%) rename {Client/src => src}/assets/icons/left-arrow-long.svg (100%) rename {Client/src => src}/assets/icons/left-arrow.svg (100%) rename {Client/src => src}/assets/icons/lock.svg (100%) rename {Client/src => src}/assets/icons/logout.svg (100%) rename {Client/src => src}/assets/icons/mail.svg (100%) rename {Client/src => src}/assets/icons/maintenance.svg (100%) rename {Client/src => src}/assets/icons/monitor-graph-line.svg (100%) rename {Client/src => src}/assets/icons/monitors.svg (100%) rename {Client/src => src}/assets/icons/open-in-new-page.svg (100%) rename {Client/src => src}/assets/icons/page-speed.svg (100%) rename {Client/src => src}/assets/icons/pause-icon.svg (100%) rename {Client/src => src}/assets/icons/performance-report.svg (100%) rename {Client/src => src}/assets/icons/radio-checked.svg (100%) rename {Client/src => src}/assets/icons/response-time-icon.svg (100%) rename {Client/src => src}/assets/icons/resume-icon.svg (100%) rename {Client/src => src}/assets/icons/right-arrow-double.svg (100%) rename {Client/src => src}/assets/icons/right-arrow.svg (100%) rename {Client/src => src}/assets/icons/ruler-icon.svg (100%) rename {Client/src => src}/assets/icons/search.svg (100%) rename {Client/src => src}/assets/icons/selector-vertical.svg (100%) rename {Client/src => src}/assets/icons/settings-bold.svg (100%) rename {Client/src => src}/assets/icons/settings.svg (100%) rename {Client/src => src}/assets/icons/slack-icon.svg (100%) rename {Client/src => src}/assets/icons/solana_logo.svg (100%) rename {Client/src => src}/assets/icons/speedometer-icon.svg (100%) rename {Client/src => src}/assets/icons/status-pages.svg (100%) rename {Client/src => src}/assets/icons/support.svg (100%) rename {Client/src => src}/assets/icons/top-right-arrow.svg (100%) rename {Client/src => src}/assets/icons/trash-bin.svg (100%) rename {Client/src => src}/assets/icons/up-arrow.svg (100%) rename {Client/src => src}/assets/icons/upt_logo.png (100%) rename {Client/src => src}/assets/icons/uptime-icon.svg (100%) rename {Client/src => src}/assets/icons/user-edit.svg (100%) rename {Client/src => src}/assets/icons/user-two.svg (100%) rename {Client/src => src}/assets/icons/user.svg (100%) rename {Client/src => src}/assets/icons/zapier-icon.svg (100%) rename {Client/src => src}/index.css (100%) rename {Client/src => src}/locales/gb.json (100%) rename {Client/src => src}/locales/tr.json (100%) rename {Client/src => src}/main.jsx (100%) rename {Client/src => src}/store.js (100%) rename Client/vite.config.js => vite.config.js (100%) diff --git a/Client/.env.production b/.env.production similarity index 100% rename from Client/.env.production rename to .env.production diff --git a/Client/.eslintrc.cjs b/.eslintrc.cjs similarity index 100% rename from Client/.eslintrc.cjs rename to .eslintrc.cjs diff --git a/.gitignore b/.gitignore index 63420e072..ebf01f87a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,28 @@ -.vite -.vscode -*.sh -!uptime.sh -*.idea -*.notes \ No newline at end of file +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env + +!env.sh \ No newline at end of file diff --git a/Client/.gitignore b/Client/.gitignore deleted file mode 100644 index ebf01f87a..000000000 --- a/Client/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -.env - -!env.sh \ No newline at end of file diff --git a/Client/README.md b/Client/README.md deleted file mode 100644 index f768e33fc..000000000 --- a/Client/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/Client/index.html b/Client/index.html deleted file mode 100644 index 2d13bd25d..000000000 --- a/Client/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - Checkmate - - - -
- - - - \ No newline at end of file diff --git a/Docker/.gitignore b/Docker/.gitignore deleted file mode 100644 index 9b501e10d..000000000 --- a/Docker/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -*.sh -!quickstart.sh -!build_images.sh -dev/mongo/data/* -dev/redis/data/* -dist/mongo/data/* -dist/redis/data/* -prod/mongo/data/* -prod/redis/data/* -*.env -prod/certbot/* \ No newline at end of file diff --git a/Docker/dev/build_images.sh b/Docker/dev/build_images.sh deleted file mode 100755 index f6171089c..000000000 --- a/Docker/dev/build_images.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Change directory to root Server directory for correct Docker Context -cd "$(dirname "$0")" -cd ../.. - -# Define an array of services and their Dockerfiles -declare -A services=( - ["uptime_client"]="./Docker/dev/client.Dockerfile" - ["uptime_database_mongo"]="./Docker/dev/mongoDB.Dockerfile" - ["uptime_redis"]="./Docker/dev/redis.Dockerfile" - ["uptime_server"]="./Docker/dev/server.Dockerfile" -) - -# Loop through each service and build the corresponding image -for service in "${!services[@]}"; do - docker build -f "${services[$service]}" -t "$service" . - - ## Check if the build succeeded - if [ $? -ne 0 ]; then - echo "Error building $service image. Exiting..." - exit 1 - fi -done - -echo "All images built successfully" diff --git a/Docker/dev/client.Dockerfile b/Docker/dev/client.Dockerfile deleted file mode 100644 index cdb1646d6..000000000 --- a/Docker/dev/client.Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:20-alpine AS build - -ENV NODE_OPTIONS="--max-old-space-size=4096" - -WORKDIR /app - -RUN apk add --no-cache \ - python3 \ - make g++ \ - gcc \ - libc-dev \ - linux-headers \ - libusb-dev \ - eudev-dev - - -COPY ../../Client/package*.json ./ - -RUN npm install - -COPY ../../Client . - -RUN npm run build-dev - -RUN npm install -g serve - -CMD ["serve","-s", "dist", "-l", "5173"] diff --git a/Docker/dev/docker-compose.yaml b/Docker/dev/docker-compose.yaml deleted file mode 100644 index f8b2c6936..000000000 --- a/Docker/dev/docker-compose.yaml +++ /dev/null @@ -1,40 +0,0 @@ -services: - client: - image: uptime_client:latest - restart: always - ports: - - "5173:5173" - - depends_on: - - server - server: - image: uptime_server:latest - restart: always - ports: - - "5000:5000" - env_file: - - server.env - depends_on: - - redis - - mongodb - redis: - image: uptime_redis:latest - restart: always - ports: - - "6379:6379" - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s - mongodb: - image: uptime_database_mongo:latest - restart: always - command: ["mongod", "--quiet"] - ports: - - "27017:27017" - volumes: - - ./mongo/data:/data/db diff --git a/Docker/dev/mongoDB.Dockerfile b/Docker/dev/mongoDB.Dockerfile deleted file mode 100644 index 969a320c2..000000000 --- a/Docker/dev/mongoDB.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM mongo -EXPOSE 27017 -CMD ["mongod"] diff --git a/Docker/dev/redis.Dockerfile b/Docker/dev/redis.Dockerfile deleted file mode 100644 index af68ec61e..000000000 --- a/Docker/dev/redis.Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM redis -EXPOSE 6379 \ No newline at end of file diff --git a/Docker/dev/server.Dockerfile b/Docker/dev/server.Dockerfile deleted file mode 100644 index 3a9ed5907..000000000 --- a/Docker/dev/server.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -COPY ../../Server/package*.json ./ - -RUN npm install - -COPY ../../Server/ ./ - -EXPOSE 5000 - -CMD ["node", "index.js"] \ No newline at end of file diff --git a/Docker/dist/build_images.sh b/Docker/dist/build_images.sh deleted file mode 100755 index 33af5efdf..000000000 --- a/Docker/dist/build_images.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Change directory to root Server directory for correct Docker Context -cd "$(dirname "$0")" -cd ../.. - -# Define an array of services and their Dockerfiles -declare -A services=( - ["bluewaveuptime/uptime_client"]="./Docker/dist/client.Dockerfile" - ["bluewaveuptime/uptime_database_mongo"]="./Docker/dist/mongoDB.Dockerfile" - ["bluewaveuptime/uptime_redis"]="./Docker/dist/redis.Dockerfile" - ["bluewaveuptime/uptime_server"]="./Docker/dist/server.Dockerfile" -) - -# Loop through each service and build the corresponding image -for service in "${!services[@]}"; do - docker build -f "${services[$service]}" -t "$service" . - - # Check if the build succeeded - if [ $? -ne 0 ]; then - echo "Error building $service image. Exiting..." - exit 1 - fi -done - -echo "All images built successfully" \ No newline at end of file diff --git a/Docker/dist/client.Dockerfile b/Docker/dist/client.Dockerfile deleted file mode 100644 index ddb5ac73d..000000000 --- a/Docker/dist/client.Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM node:20-alpine AS build - -ENV NODE_OPTIONS="--max-old-space-size=4096" - -WORKDIR /app - -RUN apk add --no-cache \ - python3 \ - make g++ \ - gcc \ - libc-dev \ - linux-headers \ - libusb-dev \ - eudev-dev - - -COPY ../../Client/package*.json ./ - -RUN npm install - -COPY ../../Client . - -RUN npm run build - - -FROM nginx:1.27.1-alpine - -COPY ./Docker/dist/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf -COPY --from=build /app/dist /usr/share/nginx/html -COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh -RUN chmod +x /docker-entrypoint.d/env.sh - -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Docker/dist/docker-compose.yaml b/Docker/dist/docker-compose.yaml deleted file mode 100644 index 420941e7e..000000000 --- a/Docker/dist/docker-compose.yaml +++ /dev/null @@ -1,46 +0,0 @@ -services: - client: - image: bluewaveuptime/uptime_client:latest - restart: always - environment: - UPTIME_APP_API_BASE_URL: "http://localhost:5000/api/v1" - UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX: "http://uptimegenie.com/" - ports: - - "80:80" - - "443:443" - depends_on: - - server - server: - image: bluewaveuptime/uptime_server:latest - restart: always - ports: - - "5000:5000" - depends_on: - - redis - - mongodb - environment: - - DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db - - REDIS_HOST=redis - # volumes: - # - /var/run/docker.sock:/var/run/docker.sock:ro - redis: - image: bluewaveuptime/uptime_redis:latest - restart: always - ports: - - "6379:6379" - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s - mongodb: - image: bluewaveuptime/uptime_database_mongo:latest - restart: always - volumes: - - ./mongo/data:/data/db - command: ["mongod", "--quiet"] - ports: - - "27017:27017" diff --git a/Docker/dist/mongoDB.Dockerfile b/Docker/dist/mongoDB.Dockerfile deleted file mode 100644 index 969a320c2..000000000 --- a/Docker/dist/mongoDB.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM mongo -EXPOSE 27017 -CMD ["mongod"] diff --git a/Docker/dist/nginx/conf.d/default.conf b/Docker/dist/nginx/conf.d/default.conf deleted file mode 100644 index 0592dcd06..000000000 --- a/Docker/dist/nginx/conf.d/default.conf +++ /dev/null @@ -1,35 +0,0 @@ -server { - listen 80; - listen [::]:80; - - server_name checkmate-demo.bluewavelabs.ca; - server_tokens off; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - location /api/ { - proxy_pass http://server:5000/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api-docs/ { - proxy_pass http://server:5000/api-docs/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} \ No newline at end of file diff --git a/Docker/dist/redis.Dockerfile b/Docker/dist/redis.Dockerfile deleted file mode 100644 index af68ec61e..000000000 --- a/Docker/dist/redis.Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM redis -EXPOSE 6379 \ No newline at end of file diff --git a/Docker/dist/server.Dockerfile b/Docker/dist/server.Dockerfile deleted file mode 100644 index 3a9ed5907..000000000 --- a/Docker/dist/server.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -COPY ../../Server/package*.json ./ - -RUN npm install - -COPY ../../Server/ ./ - -EXPOSE 5000 - -CMD ["node", "index.js"] \ No newline at end of file diff --git a/Docker/prod/build_images.sh b/Docker/prod/build_images.sh deleted file mode 100755 index c879190bc..000000000 --- a/Docker/prod/build_images.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Change directory to root directory for correct Docker Context -cd "$(dirname "$0")" -cd ../.. - -# Define an array of services and their Dockerfiles -declare -A services=( - ["uptime_client"]="./Docker/prod/client.Dockerfile" - ["uptime_database_mongo"]="./Docker/prod/mongoDB.Dockerfile" - ["uptime_redis"]="./Docker/prod/redis.Dockerfile" - ["uptime_server"]="./Docker/prod/server.Dockerfile" -) - -# Loop through each service and build the corresponding image -for service in "${!services[@]}"; do - docker build -f "${services[$service]}" -t "$service" . - - # Check if the build succeeded - if [ $? -ne 0 ]; then - echo "Error building $service image. Exiting..." - exit 1 - fi -done - -echo "All images built successfully" \ No newline at end of file diff --git a/Docker/prod/certbot-compose.yaml b/Docker/prod/certbot-compose.yaml deleted file mode 100644 index 274f15b0c..000000000 --- a/Docker/prod/certbot-compose.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3' - -services: - webserver: - image: nginx:latest - ports: - - 80:80 - - 443:443 - restart: always - volumes: - - ./nginx/conf.d/:/etc/nginx/conf.d/:ro - - ./certbot/www/:/var/www/certbot/:ro - certbot: - image: certbot/certbot:latest - volumes: - - ./certbot/www/:/var/www/certbot/:rw - - ./certbot/conf/:/etc/letsencrypt/:rw - depends_on: - - webserver diff --git a/Docker/prod/client.Dockerfile b/Docker/prod/client.Dockerfile deleted file mode 100644 index 629ec644c..000000000 --- a/Docker/prod/client.Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM node:20-alpine AS build - -ENV NODE_OPTIONS="--max-old-space-size=4096" - -WORKDIR /app - -RUN apk add --no-cache \ - python3 \ - make g++ \ - gcc \ - libc-dev \ - linux-headers \ - libusb-dev \ - eudev-dev - -COPY ../../Client/package*.json ./ - -RUN npm install - -COPY ../../Client . - -RUN npm run build - -FROM nginx:1.27.1-alpine - -COPY --from=build /app/dist /usr/share/nginx/html -COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh -RUN chmod +x /docker-entrypoint.d/env.sh -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Docker/prod/docker-compose.yaml b/Docker/prod/docker-compose.yaml deleted file mode 100644 index 0da2da996..000000000 --- a/Docker/prod/docker-compose.yaml +++ /dev/null @@ -1,57 +0,0 @@ -services: - client: - image: uptime_client:latest - restart: always - environment: - UPTIME_APP_API_BASE_URL: "https://checkmate-demo.bluewavelabs.ca/api/v1" - UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX: "http://uptimegenie.com/" - ports: - - "80:80" - - "443:443" - depends_on: - - server - volumes: - - ./nginx/conf.d:/etc/nginx/conf.d/:ro - - ./certbot/www:/var/www/certbot/:ro - - ./certbot/conf/:/etc/nginx/ssl/:ro - - certbot: - image: certbot/certbot:latest - restart: always - volumes: - - ./certbot/www/:/var/www/certbot/:rw - - ./certbot/conf/:/etc/letsencrypt/:rw - server: - image: uptime_server:latest - restart: always - ports: - - "5000:5000" - env_file: - - server.env - depends_on: - - redis - - mongodb - redis: - image: uptime_redis:latest - restart: always - ports: - - "6379:6379" - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s - mongodb: - image: uptime_database_mongo:latest - restart: always - command: ["mongod", "--quiet", "--auth"] - ports: - - "27017:27017" - volumes: - - ./mongo/data:/data/db - - ./mongo/init/create_users.js:/docker-entrypoint-initdb.d/create_users.js - env_file: - - mongo.env diff --git a/Docker/prod/mongo/init/create_users.js b/Docker/prod/mongo/init/create_users.js deleted file mode 100644 index 67a21f910..000000000 --- a/Docker/prod/mongo/init/create_users.js +++ /dev/null @@ -1,16 +0,0 @@ -var username = process.env.USERNAME_ENV_VAR; -var password = process.env.PASSWORD_ENV_VAR; - -db = db.getSiblingDB("uptime_db"); - -db.createUser({ - user: username, - pwd: password, - roles: [ - { - role: "readWrite", - db: "uptime_db", - }, - ], -}); -print("User uptime_user created successfully"); diff --git a/Docker/prod/mongoDB.Dockerfile b/Docker/prod/mongoDB.Dockerfile deleted file mode 100644 index 80c8b9c8f..000000000 --- a/Docker/prod/mongoDB.Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM mongo -COPY ./Docker/prod/mongo/init/create_users.js /docker-entrypoint-initdb.d/ -EXPOSE 27017 -CMD ["mongod"] diff --git a/Docker/prod/nginx/conf.d/cerbot b/Docker/prod/nginx/conf.d/cerbot deleted file mode 100644 index f26cfeb23..000000000 --- a/Docker/prod/nginx/conf.d/cerbot +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - listen [::]:80; - - server_name checkmate-demo.bluewavelabs.ca www.checkmate-demo.bluewavelabs.ca; - server_tokens off; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - return 301 https://[domain-name]$request_uri; - } -} diff --git a/Docker/prod/nginx/conf.d/default.conf b/Docker/prod/nginx/conf.d/default.conf deleted file mode 100644 index 98e1bd97d..000000000 --- a/Docker/prod/nginx/conf.d/default.conf +++ /dev/null @@ -1,69 +0,0 @@ -server { - listen 80; - listen [::]:80; - - server_name checkmate-demo.bluewavelabs.ca; - server_tokens off; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - location /api/ { - proxy_pass http://server:5000/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api-docs/ { - proxy_pass http://server:5000/api-docs/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} - -server { - listen 443 default_server ssl http2; - listen [::]:443 ssl http2; - - server_name checkmate-demo.bluewavelabs.ca; - - ssl_certificate /etc/nginx/ssl/live/checkmate-demo.bluewavelabs.ca/fullchain.pem; - ssl_certificate_key /etc/nginx/ssl/live/checkmate-demo.bluewavelabs.ca/privkey.pem; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - location /api/ { - proxy_pass http://server:5000/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api-docs/ { - proxy_pass http://server:5000/api-docs/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} diff --git a/Docker/prod/redis.Dockerfile b/Docker/prod/redis.Dockerfile deleted file mode 100644 index af68ec61e..000000000 --- a/Docker/prod/redis.Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM redis -EXPOSE 6379 \ No newline at end of file diff --git a/Docker/prod/server.Dockerfile b/Docker/prod/server.Dockerfile deleted file mode 100644 index 707b774e7..000000000 --- a/Docker/prod/server.Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:20-alpine - -ENV NODE_OPTIONS="--max-old-space-size=2048" - -WORKDIR /app - -COPY ./Server/package*.json ./ - -RUN npm install - -COPY ./Server/ ./ - -EXPOSE 5000 - -CMD ["node", "index.js"] \ No newline at end of file diff --git a/Docker/test/build_images.sh b/Docker/test/build_images.sh deleted file mode 100755 index c879190bc..000000000 --- a/Docker/test/build_images.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Change directory to root directory for correct Docker Context -cd "$(dirname "$0")" -cd ../.. - -# Define an array of services and their Dockerfiles -declare -A services=( - ["uptime_client"]="./Docker/prod/client.Dockerfile" - ["uptime_database_mongo"]="./Docker/prod/mongoDB.Dockerfile" - ["uptime_redis"]="./Docker/prod/redis.Dockerfile" - ["uptime_server"]="./Docker/prod/server.Dockerfile" -) - -# Loop through each service and build the corresponding image -for service in "${!services[@]}"; do - docker build -f "${services[$service]}" -t "$service" . - - # Check if the build succeeded - if [ $? -ne 0 ]; then - echo "Error building $service image. Exiting..." - exit 1 - fi -done - -echo "All images built successfully" \ No newline at end of file diff --git a/Docker/test/cerbot-compose.yaml b/Docker/test/cerbot-compose.yaml deleted file mode 100644 index 9220d07a1..000000000 --- a/Docker/test/cerbot-compose.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -services: - webserver: - image: nginx:latest - ports: - - 80:80 - - 443:443 - restart: always - volumes: - - ./nginx/conf.d/:/etc/nginx/conf.d/:ro - - ./certbot/www/:/var/www/certbot/:ro - certbot: - image: certbot/certbot:latest - volumes: - - ./certbot/www/:/var/www/certbot/:rw - - ./certbot/conf/:/etc/letsencrypt/:rw - depends_on: - - webserver diff --git a/Docker/test/client.Dockerfile b/Docker/test/client.Dockerfile deleted file mode 100644 index 215d20873..000000000 --- a/Docker/test/client.Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM node:20-alpine AS build - -ENV NODE_OPTIONS="--max-old-space-size=4096" - -WORKDIR /app - -RUN apk add --no-cache \ - python3 \ - make g++ \ - gcc \ - libc-dev \ - linux-headers \ - libusb-dev \ - eudev-dev - - -COPY ../../Client/package*.json ./ - -RUN npm install - -COPY ../../Client . - -RUN npm run build - -FROM nginx:1.27.1-alpine - -COPY --from=build /app/dist /usr/share/nginx/html -COPY --from=build /app/env.sh /docker-entrypoint.d/env.sh -RUN chmod +x /docker-entrypoint.d/env.sh -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Docker/test/docker-compose.yaml b/Docker/test/docker-compose.yaml deleted file mode 100644 index 2c38e8af3..000000000 --- a/Docker/test/docker-compose.yaml +++ /dev/null @@ -1,57 +0,0 @@ -services: - client: - image: uptime_client:latest - restart: always - environment: - UPTIME_APP_API_BASE_URL: "https://checkmate-test.bluewavelabs.ca/api/v1" - UPTIME_STATUS_PAGE_SUBDOMAIN_PREFIX: "http://uptimegenie.com/" - ports: - - "80:80" - - "443:443" - depends_on: - - server - volumes: - - ./nginx/conf.d:/etc/nginx/conf.d/:ro - - ./certbot/www:/var/www/certbot/:ro - - ./certbot/conf/:/etc/nginx/ssl/:ro - - certbot: - image: certbot/certbot:latest - restart: always - volumes: - - ./certbot/www/:/var/www/certbot/:rw - - ./certbot/conf/:/etc/letsencrypt/:rw - server: - image: uptime_server:latest - restart: always - ports: - - "5000:5000" - env_file: - - server.env - depends_on: - - redis - - mongodb - redis: - image: uptime_redis:latest - restart: always - ports: - - "6379:6379" - volumes: - - ./redis/data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 5s - mongodb: - image: uptime_database_mongo:latest - restart: always - command: ["mongod", "--quiet", "--replSet", "rs0", "--bind_ip_all"] - ports: - - "27017:27017" - volumes: - - ./mongo/data:/data/db - - ./mongo/init/create_users.js:/docker-entrypoint-initdb.d/create_users.js - env_file: - - mongo.env diff --git a/Docker/test/mongo/init/create_users.js b/Docker/test/mongo/init/create_users.js deleted file mode 100644 index 67a21f910..000000000 --- a/Docker/test/mongo/init/create_users.js +++ /dev/null @@ -1,16 +0,0 @@ -var username = process.env.USERNAME_ENV_VAR; -var password = process.env.PASSWORD_ENV_VAR; - -db = db.getSiblingDB("uptime_db"); - -db.createUser({ - user: username, - pwd: password, - roles: [ - { - role: "readWrite", - db: "uptime_db", - }, - ], -}); -print("User uptime_user created successfully"); diff --git a/Docker/test/mongoDB.Dockerfile b/Docker/test/mongoDB.Dockerfile deleted file mode 100644 index 80c8b9c8f..000000000 --- a/Docker/test/mongoDB.Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM mongo -COPY ./Docker/prod/mongo/init/create_users.js /docker-entrypoint-initdb.d/ -EXPOSE 27017 -CMD ["mongod"] diff --git a/Docker/test/nginx/conf.d/certbot b/Docker/test/nginx/conf.d/certbot deleted file mode 100644 index eba24638c..000000000 --- a/Docker/test/nginx/conf.d/certbot +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - listen [::]:80; - - server_name checkmate-test.bluewavelabs.ca www.checkmate-test.bluewavelabs.ca; - server_tokens off; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - return 301 https://[domain-name]$request_uri; - } -} diff --git a/Docker/test/nginx/conf.d/default.conf b/Docker/test/nginx/conf.d/default.conf deleted file mode 100644 index 1533dd35e..000000000 --- a/Docker/test/nginx/conf.d/default.conf +++ /dev/null @@ -1,69 +0,0 @@ -server { - listen 80; - listen [::]:80; - - server_name checkmate-test.bluewavelabs.ca; - server_tokens off; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - location /api/ { - proxy_pass http://server:5000/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api-docs/ { - proxy_pass http://server:5000/api-docs/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} - -server { - listen 443 default_server ssl http2; - listen [::]:443 ssl http2; - - server_name checkmate-test.bluewavelabs.ca; - - ssl_certificate /etc/nginx/ssl/live/checkmate-test.bluewavelabs.ca/fullchain.pem; - ssl_certificate_key /etc/nginx/ssl/live/checkmate-test.bluewavelabs.ca/privkey.pem; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - location /api/ { - proxy_pass http://server:5000/api/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /api-docs/ { - proxy_pass http://server:5000/api-docs/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} diff --git a/Docker/test/redis.Dockerfile b/Docker/test/redis.Dockerfile deleted file mode 100644 index af68ec61e..000000000 --- a/Docker/test/redis.Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM redis -EXPOSE 6379 \ No newline at end of file diff --git a/Docker/test/server.Dockerfile b/Docker/test/server.Dockerfile deleted file mode 100644 index 5d4ea7364..000000000 --- a/Docker/test/server.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -COPY ./Server/package*.json ./ - -RUN npm install - -COPY ./Server/ ./ - -EXPOSE 5000 - -CMD ["node", "index.js"] \ No newline at end of file diff --git a/README.md b/README.md index e6cadaeda..f768e33fc 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,8 @@ +# React + Vite +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. -

bluewave-labs%2Fcheckmate | Trendshift

- -![](https://img.shields.io/github/license/bluewave-labs/checkmate) -![](https://img.shields.io/github/repo-size/bluewave-labs/checkmate) -![](https://img.shields.io/github/commit-activity/m/bluewave-labs/checkmate) -![](https://img.shields.io/github/last-commit/bluewave-labs/checkmate) -![](https://img.shields.io/github/languages/top/bluewave-labs/checkmate) -![](https://img.shields.io/github/issues/bluewave-labs/checkmate) -![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) +Currently, two official plugins are available: - -

Checkmate

- -

An open source uptime and infrastructure monitoring application

- -![dashboard](https://github.com/user-attachments/assets/252d6047-522b-4576-8f14-233510e464b8) - -Checkmate is an open source monitoring tool used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. - -Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insigths about your servers' CPU, RAM, disk and temperature status. - -Checkmate has ben stress tested with 1000+ active monitors without any particular issues or performance bottlenecks. - -We **love** what we are building here, and we contibuously learn a few things about Reactjs, Nodejs, MongoDB and Docker while building Checkmate. - -## 📦 Demo - -See [Checkmate](https://checkmate-demo.bluewavelabs.ca/) in action. The username is uptimedemo@demo.com and the password is Demouser1! (just a note that we update the demo server from time to time, so if it doesn't work for you, please ping us on Discussions channel). - -## 🔗 User's guide - -Usage instructions can be found [here](https://docs.checkmate.so/). It's still WIP and some of the information there might be outdated. Rest assured, we are doing our best! :) - -## 🛠️ Installation - -See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/quickstart). Alternatively, you can also use [Coolify](https://coolify.io/) or [Elestio](https://elest.io/open-source/checkmate) for a one click Docker deployment. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions. - -## 🚀 Performance -Thanks to extensive optimizations, Checkmate operates with an exceptionally small memory footprint, requiring minimal memory and CPU resources. Here’s the memory usage of a Node.js instance running on a server that monitors 323 servers every minute: - -![image](https://github.com/user-attachments/assets/37e04a75-d83a-488f-b25c-025511b492c9) - -You can see the memory footprint of MongoDB and Redis on the same server (398Mb and 15Mb) for the same amount of servers: - -![image](https://github.com/user-attachments/assets/3b469e85-e675-4040-a162-3f24c1afc751) - - -## 💚 Questions & ideas - -If you have any questions, suggestions or comments, please use our [Discord channel](https://discord.gg/NAb6H3UTjK). We've also launched our [Discussions](https://github.com/bluewave-labs/bluewave-uptime/discussions) page! Feel free to ask questions or share your ideas—we'd love to hear from you! - -## 🧩 Features - -- Completely open source, deployable on your servers -- Website monitoring -- Page speed monitoring -- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture) -- Docker monitoring -- Ping monitoring -- SSL monitoring -- Port monitoring -- Incidents at a glance -- E-mail notifications -- Scheduled maintenance - -**Short term roadmap:** - -- Global (distributed) uptime checking on Solana network (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1593 -- Status pages (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1131 -- Translations (i18n) (**in progress**) -- Better notification options (Webhooks, Discord, Telegram, Slack) (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1545 -- Command line interface (CLI) (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1558 -- JSON query monitoring https://github.com/bluewave-labs/Checkmate/issues/1573 -- Tagging/grouping monitors https://github.com/bluewave-labs/Checkmate/issues/1546 -- More configuration options -- DNS monitoring - -## 🏗️ Screenshots -

-server -

-

-uptime -

-

-page speed -

- -## 🏗️ Tech stack - -- [ReactJs](https://react.dev/) -- [MUI (React framework)](https://mui.com/) -- [Node.js](https://nodejs.org/en) -- [MongoDB](https://mongodb.com) -- [Recharts](https://recharts.org) -- Lots of other open source components! - -## A few links - -- If you would like to support us, please consider giving it a ⭐ and click on "watch". -- Have a question or suggestion for the roadmap/featureset? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum. -- Need a ping when there's a new release? Use [Newreleases](https://newreleases.io/), a free service to track releases. -- Watch a Checkmate [installation and usage video](https://www.youtube.com/watch?v=GfFOc0xHIwY) - -## 🤝 Contributing - -We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 3700+ stars and attracted 45+ contributors from around the globe. So, don’t hold back — jump in, contribute and learn with us! - -Here's how you can contribute: - -0. Star this repo :) -1. Check [Contributor's guideline](https://github.com/bluewave-labs/bluewave-uptime/blob/master/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag. -2. Optionally, read [project structure](https://docs.checkmate.so/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview). -3. Have a look at our Figma designs [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1) if you are going to use one of our designs. We encourage you to copy to your own Figma page, then work on it as it is read-only. -4. Open an issue if you believe you've encountered a bug. -5. Check for good-first-issue's if you are a newcomer. -6. Make a pull request to add new features/make quality-of-life improvements/fix bugs. - - - - - -## 💰 Our sponsors - -Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca - -[![Star History Chart](https://api.star-history.com/svg?repos=bluewave-labs/checkmate&type=Date)](https://star-history.com/#bluewave-labs/bluewave-uptime&Date) - -Also check other developer and contributor-friendly projects of BlueWave: - -- [LangRoute](https://github.com/bluewave-labs/langroute), an LLM proxy and gateway -- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom. -- [Headcount](https://github.com/bluewave-labs/bluewave-hrm), a complete Human Resource Management platform. -- [Guidefox](https://github.com/bluewave-labs/guidefox), an application that helps new users learn how to use your product via hints, tours, popups and banners. -- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform. - -![image](https://badges.pufler.dev/visits/bluewave-labs/checkmate) since 14 Jan, 2025 +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/Server/.dockerignore b/Server/.dockerignore deleted file mode 100644 index d88f9054e..000000000 --- a/Server/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -./docker \ No newline at end of file diff --git a/Server/.gitignore b/Server/.gitignore deleted file mode 100644 index 6dc7e9e0d..000000000 --- a/Server/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -.env -*.log -*.sh -.nyc_output -coverage -.clinic \ No newline at end of file diff --git a/Server/.mocharc.cjs b/Server/.mocharc.cjs deleted file mode 100644 index e4c8f0dd7..000000000 --- a/Server/.mocharc.cjs +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - require: ["esm", "chai/register-expect.js"], // Include Chai's "expect" interface globally - spec: "tests/**/*.test.js", // Specify test files - timeout: 5000, // Set test-case timeout in milliseconds - recursive: true, // Include subdirectories - reporter: "spec", // Use the "spec" reporter - exit: true, // Force Mocha to quit after tests complete -}; diff --git a/Server/.nycrc b/Server/.nycrc deleted file mode 100644 index 96f20d795..000000000 --- a/Server/.nycrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "all": true, - "include": ["controllers/*.js", "utils/*.js", "service/*.js", "db/mongo/modules/*.js"], - "exclude": ["**/*.test.js"], - "reporter": ["html", "text", "lcov"], - "sourceMap": false, - "instrument": true -} diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js deleted file mode 100644 index 94952e3c7..000000000 --- a/Server/controllers/authController.js +++ /dev/null @@ -1,523 +0,0 @@ -import { - registrationBodyValidation, - loginValidation, - editUserParamValidation, - editUserBodyValidation, - recoveryValidation, - recoveryTokenValidation, - newPasswordValidation, -} from "../validation/joi.js"; -import logger from "../utils/logger.js"; -import jwt from "jsonwebtoken"; -import { getTokenFromHeaders, tokenType } from "../utils/utils.js"; -import crypto from "crypto"; -import { handleValidationError, handleError } from "./controllerUtils.js"; -const SERVICE_NAME = "authController"; - -class AuthController { - constructor(db, settingsService, emailService, jobQueue, stringService) { - this.db = db; - this.settingsService = settingsService; - this.emailService = emailService; - this.jobQueue = jobQueue; - this.stringService = stringService; - } - - /** - * Creates and returns JWT token with an arbitrary payload - * @function - * @param {Object} payload - * @param {tokenType} typeOfToken - Whether to generate refresh token with long TTL or access token with short TTL. - * @param {Object} appSettings - * @returns {String} - * @throws {Error} - */ - issueToken = (payload, typeOfToken, appSettings) => { - try { - const tokenTTL = - typeOfToken === tokenType.REFRESH_TOKEN - ? (appSettings?.refreshTokenTTL ?? "7d") - : (appSettings?.jwtTTL ?? "2h"); - const tokenSecret = - typeOfToken === tokenType.REFRESH_TOKEN - ? appSettings?.refreshTokenSecret - : appSettings?.jwtSecret; - const payloadData = typeOfToken === tokenType.REFRESH_TOKEN ? {} : payload; - - return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL }); - } catch (error) { - throw handleError(error, SERVICE_NAME, "issueToken"); - } - }; - - /** - * Registers a new user. If the user is the first account, a JWT secret is created. If not, an invite token is required. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.inviteToken - The invite token for registration. - * @property {Object} req.file - The file object for the user's profile image. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the user, the created user data, and a JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - registerUser = async (req, res, next) => { - try { - await registrationBodyValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - // Create a new user - try { - const { inviteToken } = req.body; - // If superAdmin exists, a token should be attached to all further register requests - const superAdminExists = await this.db.checkSuperadmin(req, res); - if (superAdminExists) { - await this.db.getInviteTokenAndDelete(inviteToken); - } else { - // This is the first account, create JWT secret to use if one is not supplied by env - const jwtSecret = crypto.randomBytes(64).toString("hex"); - await this.db.updateAppSettings({ jwtSecret }); - } - - const newUser = await this.db.insertUser({ ...req.body }, req.file); - logger.info({ - message: this.stringService.authCreateUser, - service: SERVICE_NAME, - details: newUser._id, - }); - - const userForToken = { ...newUser._doc }; - delete userForToken.profileImage; - delete userForToken.avatarImage; - - const appSettings = await this.settingsService.getSettings(); - - const token = this.issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings); - const refreshToken = this.issueToken({}, tokenType.REFRESH_TOKEN, appSettings); - - this.emailService - .buildAndSendEmail( - "welcomeEmailTemplate", - { name: newUser.firstName }, - newUser.email, - "Welcome to Uptime Monitor" - ) - .catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "registerUser", - stack: error.stack, - }); - }); - - res.success({ - msg: this.stringService.authCreateUser, - data: { user: newUser, token: token, refreshToken: refreshToken }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "registerController")); - } - }; - - /** - * Logs in a user by validating the user's credentials and issuing a JWT token. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user. - * @property {string} req.body.password - The password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the login of the user, the user data (without password and avatar image), and a JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect. - */ - loginUser = async (req, res, next) => { - try { - await loginValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - try { - const { email, password } = req.body; - - // Check if user exists - const user = await this.db.getUserByEmail(email); - - // Compare password - const match = await user.comparePassword(password); - if (match !== true) { - const error = new Error(this.stringService.authIncorrectPassword); - error.status = 401; - next(error); - return; - } - - // Remove password from user object. Should this be abstracted to DB layer? - const userWithoutPassword = { ...user._doc }; - delete userWithoutPassword.password; - delete userWithoutPassword.avatarImage; - - // Happy path, return token - const appSettings = await this.settingsService.getSettings(); - const token = this.issueToken( - userWithoutPassword, - tokenType.ACCESS_TOKEN, - appSettings - ); - const refreshToken = this.issueToken({}, tokenType.REFRESH_TOKEN, appSettings); - // reset avatar image - userWithoutPassword.avatarImage = user.avatarImage; - - return res.success({ - msg: this.stringService.authLoginUser, - data: { - user: userWithoutPassword, - token: token, - refreshToken: refreshToken, - }, - }); - } catch (error) { - error.status = 401; - next(handleError(error, SERVICE_NAME, "loginUser")); - } - }; - - /** - * Generates new auth token if the refresh token is valid - * @async - * @param {Express.Request} req - The Express request object. - * @property {Object} req.headers - The parameter of the request. - * @param {Express.Response} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating new auth token is generated. - * @throws {Error} If there is an error during the process such as any of the token is not received - */ - refreshAuthToken = async (req, res, next) => { - - try { - // check for refreshToken - const refreshToken = req.headers["x-refresh-token"]; - - if (!refreshToken) { - // No refresh token provided - const error = new Error(this.stringService.noRefreshToken); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "refreshAuthToken"; - return next(error); - } - - // Verify refresh token - const appSettings = await this.settingsService.getSettings(); - const { refreshTokenSecret } = appSettings; - jwt.verify(refreshToken, refreshTokenSecret, async (refreshErr, refreshDecoded) => { - if (refreshErr) { - // Invalid or expired refresh token, trigger logout - const errorMessage = - refreshErr.name === "TokenExpiredError" - ? this.stringService.expiredAuthToken - : this.stringService.invalidAuthToken; - const error = new Error(errorMessage); - error.status = 401; - error.service = SERVICE_NAME; - return next(error); - } - }); - // Refresh token is valid and unexpired, generate new access token - const oldAuthToken = getTokenFromHeaders(req.headers); - const { jwtSecret } = await this.settingsService.getSettings(); - const payloadData = jwt.verify(oldAuthToken, jwtSecret, { ignoreExpiration: true }); - // delete old token related data - delete payloadData.iat; - delete payloadData.exp; - const newAuthToken = this.issueToken( - payloadData, - tokenType.ACCESS_TOKEN, - appSettings - ); - - return res.success({ - msg: this.stringService.authTokenRefreshed, - data: { user: payloadData, token: newAuthToken, refreshToken: refreshToken }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "refreshAuthToken")); - } - }; - /** - * Edits a user's information. If the user wants to change their password, the current password is checked before updating to the new password. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.userId - The ID of the user to be edited. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.password - The current password of the user. - * @property {string} req.body.newPassword - The new password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the update of the user, and the updated user data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403). - */ - editUser = async (req, res, next) => { - - try { - await editUserParamValidation.validateAsync(req.params); - await editUserBodyValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - // TODO is this neccessary any longer? Verify ownership middleware should handle this - if (req.params.userId !== req.user._id.toString()) { - const error = new Error(this.stringService.unauthorized); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - - try { - // Change Password check - if (req.body.password && req.body.newPassword) { - // Get token from headers - const token = getTokenFromHeaders(req.headers); - // Get email from token - const { jwtSecret } = this.settingsService.getSettings(); - const { email } = jwt.verify(token, jwtSecret); - // Add user email to body for DB operation - req.body.email = email; - // Get user - const user = await this.db.getUserByEmail(email); - // Compare passwords - const match = await user.comparePassword(req.body.password); - // If not a match, throw a 403 - // 403 instead of 401 to avoid triggering axios interceptor - if (!match) { - const error = new Error(this.stringService.authIncorrectPassword); - error.status = 403; - next(error); - return; - } - // If a match, update the password - req.body.password = req.body.newPassword; - } - - const updatedUser = await this.db.updateUser(req, res); - res.success({ - msg: this.stringService.authUpdateUser, - data: updatedUser, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "userEditController")); - } - }; - - /** - * Checks if a superadmin account exists in the database. - * @async - * @param {Object} req - The Express request object. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the existence of a superadmin, and a boolean indicating the existence of a superadmin. - * @throws {Error} If there is an error during the process. - */ - checkSuperadminExists = async (req, res, next) => { - try { - const superAdminExists = await this.db.checkSuperadmin(req, res); - - return res.success({ - msg: this.stringService.authAdminExists, - data: superAdminExists, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "checkSuperadminController")); - } - }; - /** - * Requests a recovery token for a user. The user's email is validated and a recovery token is created and sent via email. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user requesting recovery. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the recovery token, and the message ID of the sent email. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - requestRecovery = async (req, res, next) => { - try { - await recoveryValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - try { - const { email } = req.body; - const user = await this.db.getUserByEmail(email); - const recoveryToken = await this.db.requestRecoveryToken(req, res); - const name = user.firstName; - const { clientHost } = this.settingsService.getSettings(); - const url = `${clientHost}/set-new-password/${recoveryToken.token}`; - const msgId = await this.emailService.buildAndSendEmail( - "passwordResetTemplate", - { - name, - email, - url, - }, - email, - "Checkmate Password Reset" - ); - - return res.success({ - msg: this.stringService.authCreateRecoveryToken, - data: msgId, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "recoveryRequestController")); - } - }; - /** - * Validates a recovery token. The recovery token is validated and if valid, a success message is returned. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.token - The recovery token to be validated. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the validation of the recovery token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - validateRecovery = async (req, res, next) => { - try { - await recoveryTokenValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - - try { - await this.db.validateRecoveryToken(req, res); - - return res.success({ - msg: this.stringService.authVerifyRecoveryToken, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController")); - } - }; - - /** - * Resets a user's password. The new password is validated and if valid, the user's password is updated in the database and a new JWT token is issued. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.token - The recovery token. - * @property {string} req.body.password - The new password of the user. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the reset of the password, the updated user data (without password and avatar image), and a new JWT token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - resetPassword = async (req, res, next) => { - try { - await newPasswordValidation.validateAsync(req.body); - } catch (error) { - const validationError = handleValidationError(error, SERVICE_NAME); - next(validationError); - return; - } - try { - const user = await this.db.resetPassword(req, res); - const appSettings = await this.settingsService.getSettings(); - const token = this.issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings); - - return res.success({ - msg: this.stringService.authResetPassword, - data: { user, token }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "resetPasswordController")); - } - }; - - /** - * Deletes a user and all associated monitors, checks, and alerts. - * - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {Function} next - The next middleware function. - * @returns {Object} The response object with success status and message. - * @throws {Error} If user validation fails or user is not found in the database. - */ - deleteUser = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const decodedToken = jwt.decode(token); - const { email } = decodedToken; - - // Check if the user exists - const user = await this.db.getUserByEmail(email); - // 1. Find all the monitors associated with the team ID if superadmin - - const result = await this.db.getMonitorsByTeamId({ - params: { teamId: user.teamId }, - }); - - if (user.role.includes("superadmin")) { - //2. Remove all jobs, delete checks and alerts - result?.monitors.length > 0 && - (await Promise.all( - result.monitors.map(async (monitor) => { - await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); - }) - )); - - // 3. Delete team - await this.db.deleteTeam(user.teamId); - // 4. Delete all other team members - await this.db.deleteAllOtherUsers(); - // 5. Delete each monitor - await this.db.deleteMonitorsByUserId(user._id); - } - // 6. Delete the user by id - await this.db.deleteUser(user._id); - - return res.success({ - msg: this.stringService.authDeleteUser, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteUserController")); - } - }; - - getAllUsers = async (req, res, next) => { - try { - const allUsers = await this.db.getAllUsers(req, res); - - return res.success({ - msg: this.stringService.authGetAllUsers, - data: allUsers, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllUsersController")); - } - }; -} - -export default AuthController; diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js deleted file mode 100644 index 2addd7359..000000000 --- a/Server/controllers/checkController.js +++ /dev/null @@ -1,154 +0,0 @@ -import { - createCheckParamValidation, - createCheckBodyValidation, - getChecksParamValidation, - getChecksQueryValidation, - getTeamChecksParamValidation, - getTeamChecksQueryValidation, - deleteChecksParamValidation, - deleteChecksByTeamIdParamValidation, - updateChecksTTLBodyValidation, -} from "../validation/joi.js"; -import jwt from "jsonwebtoken"; -import { getTokenFromHeaders } from "../utils/utils.js"; -import { handleValidationError, handleError } from "./controllerUtils.js"; - -const SERVICE_NAME = "checkController"; - -class CheckController { - constructor(db, settingsService, stringService) { - this.db = db; - this.settingsService = settingsService; - this.stringService = stringService; - } - - createCheck = async (req, res, next) => { - try { - await createCheckParamValidation.validateAsync(req.params); - await createCheckBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const checkData = { ...req.body }; - const check = await this.db.createCheck(checkData); - - return res.success({ - msg: this.stringService.checkCreate, - data: check, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createCheck")); - } - }; - - getChecksByMonitor = async (req, res, next) => { - try { - await getChecksParamValidation.validateAsync(req.params); - await getChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const result = await this.db.getChecksByMonitor(req); - - return res.success({ - msg: this.stringService.checkGet, - data: result, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getChecks")); - } - }; - - getChecksByTeam = async (req, res, next) => { - try { - await getTeamChecksParamValidation.validateAsync(req.params); - await getTeamChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const checkData = await this.db.getChecksByTeam(req); - - return res.success({ - msg: this.stringService.checkGet, - data: checkData, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getTeamChecks")); - } - }; - - deleteChecks = async (req, res, next) => { - try { - await deleteChecksParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const deletedCount = await this.db.deleteChecks(req.params.monitorId); - - return res.success({ - msg: this.stringService.checkDelete, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecks")); - } - }; - - deleteChecksByTeamId = async (req, res, next) => { - try { - await deleteChecksByTeamIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const deletedCount = await this.db.deleteChecksByTeamId(req.params.teamId); - - return res.success({ - msg: this.stringService.checkDelete, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId")); - } - }; - - updateChecksTTL = async (req, res, next) => { - const SECONDS_PER_DAY = 86400; - - try { - await updateChecksTTLBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - // Get user's teamId - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; - await this.db.updateChecksTTL(teamId, ttl); - - return res.success({ - msg: this.stringService.checkUpdateTTL, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateTTL")); - } - }; -} -export default CheckController; diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js deleted file mode 100644 index ec7f1122f..000000000 --- a/Server/controllers/controllerUtils.js +++ /dev/null @@ -1,26 +0,0 @@ -const handleValidationError = (error, serviceName) => { - error.status = 422; - error.service = serviceName; - error.message = error.details?.[0]?.message || error.message || "Validation Error"; - return error; -}; - -const handleError = (error, serviceName, method, status = 500) => { - error.status === undefined ? (error.status = status) : null; - error.service === undefined ? (error.service = serviceName) : null; - error.method === undefined ? (error.method = method) : null; - return error; -}; - -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 new Error("Certificate not found"); - } - return cert; -}; - -export { handleValidationError, handleError, fetchMonitorCertificate }; diff --git a/Server/controllers/distributedUptimeController.js b/Server/controllers/distributedUptimeController.js deleted file mode 100644 index 78ccafa75..000000000 --- a/Server/controllers/distributedUptimeController.js +++ /dev/null @@ -1,212 +0,0 @@ -import { handleError } from "./controllerUtils.js"; -import Monitor from "../db/models/Monitor.js"; -import DistributedUptimeCheck from "../db/models/DistributedUptimeCheck.js"; -const SERVICE_NAME = "DistributedUptimeQueueController"; - -class DistributedUptimeController { - constructor(db, http, statusService) { - this.db = db; - this.http = http; - this.statusService = statusService; - this.resultsCallback = this.resultsCallback.bind(this); - this.getDistributedUptimeMonitors = this.getDistributedUptimeMonitors.bind(this); - this.getDistributedUptimeMonitorDetails = - this.getDistributedUptimeMonitorDetails.bind(this); - } - - async resultsCallback(req, res, next) { - try { - const { id, result } = req.body; - // Calculate response time - const { - first_byte_took, - body_read_took, - dns_took, - conn_took, - connect_took, - tls_took, - status_code, - error, - } = result; - - // Calculate response time - const responseTime = first_byte_took / 1_000_000; - - // Calculate if server is up or down - const isErrorStatus = status_code >= 400; - const hasError = error !== ""; - - const status = isErrorStatus || hasError ? false : true; - - // Build response - const distributedUptimeResponse = { - monitorId: id, - type: "distributed_http", - payload: result, - status, - code: status_code, - responseTime, - first_byte_took, - body_read_took, - dns_took, - conn_took, - connect_took, - tls_took, - }; - if (error) { - const code = status_code || this.NETWORK_ERROR; - distributedUptimeResponse.code = code; - distributedUptimeResponse.message = - this.http.STATUS_CODES[code] || "Network Error"; - } else { - distributedUptimeResponse.message = this.http.STATUS_CODES[status_code]; - } - - await this.statusService.updateStatus(distributedUptimeResponse); - - res.status(200).json({ message: "OK" }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "resultsCallback")); - } - } - - async getDistributedUptimeMonitors(req, res, next) { - try { - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Cache-Control", "no-cache"); - res.setHeader("Connection", "keep-alive"); - res.setHeader("Access-Control-Allow-Origin", "*"); - - const BATCH_DELAY = 1000; - let batchTimeout = null; - let opInProgress = false; - - // Do things here - const notifyChange = async () => { - if (opInProgress) { - // Get data - const monitors = await this.db.getMonitorsByTeamId(req); - res.write(`data: ${JSON.stringify({ monitors })}\n\n`); - opInProgress = false; - } - batchTimeout = null; - }; - - const handleChange = () => { - opInProgress = true; - if (batchTimeout) clearTimeout(batchTimeout); - batchTimeout = setTimeout(notifyChange, BATCH_DELAY); - }; - - const monitorStream = Monitor.watch( - [{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }], - { fullDocument: "updateLookup" } - ); - - const checksStream = DistributedUptimeCheck.watch( - [{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }], - { fullDocument: "updateLookup" } - ); - - monitorStream.on("change", handleChange); - checksStream.on("change", handleChange); - - // Send initial data - const monitors = await this.db.getMonitorsByTeamId(req); - res.write(`data: ${JSON.stringify({ monitors })}\n\n`); - - // Handle client disconnect - req.on("close", () => { - if (batchTimeout) { - clearTimeout(batchTimeout); - } - monitorStream.close(); - checksStream.close(); - clearInterval(keepAlive); - }); - - // Keep connection alive - const keepAlive = setInterval(() => { - res.write(": keepalive\n\n"); - }, 30000); - - // Clean up on close - req.on("close", () => { - clearInterval(keepAlive); - }); - } catch (error) { - console.log(error); - next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitors")); - } - } - - async getDistributedUptimeMonitorDetails(req, res, next) { - try { - res.setHeader("Content-Type", "text/event-stream"); - res.setHeader("Cache-Control", "no-cache"); - res.setHeader("Connection", "keep-alive"); - res.setHeader("Access-Control-Allow-Origin", "*"); - - const BATCH_DELAY = 1000; - let batchTimeout = null; - let opInProgress = false; - - // Do things here - const notifyChange = async () => { - try { - if (opInProgress) { - // Get data - const monitor = await this.db.getDistributedUptimeDetailsById(req); - res.write(`data: ${JSON.stringify({ monitor })}\n\n`); - opInProgress = false; - } - batchTimeout = null; - } catch (error) { - console.error("Error in notifyChange:", error); - opInProgress = false; - batchTimeout = null; - next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitorDetails")); - } - }; - - const handleChange = () => { - opInProgress = true; - if (batchTimeout) clearTimeout(batchTimeout); - batchTimeout = setTimeout(notifyChange, BATCH_DELAY); - }; - - const checksStream = DistributedUptimeCheck.watch( - [{ $match: { operationType: { $in: ["insert", "update", "delete"] } } }], - { fullDocument: "updateLookup" } - ); - - checksStream.on("change", handleChange); - - // Send initial data - const monitor = await this.db.getDistributedUptimeDetailsById(req); - res.write(`data: ${JSON.stringify({ monitor })}\n\n`); - - // Handle client disconnect - req.on("close", () => { - if (batchTimeout) { - clearTimeout(batchTimeout); - } - checksStream.close(); - clearInterval(keepAlive); - }); - - // Keep connection alive - const keepAlive = setInterval(() => { - res.write(": keepalive\n\n"); - }, 30000); - - // Clean up on close - req.on("close", () => { - clearInterval(keepAlive); - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getDistributedUptimeMonitorDetails")); - } - } -} -export default DistributedUptimeController; diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js deleted file mode 100644 index 40d045432..000000000 --- a/Server/controllers/inviteController.js +++ /dev/null @@ -1,99 +0,0 @@ -import { - inviteRoleValidation, - inviteBodyValidation, - inviteVerificationBodyValidation, -} from "../validation/joi.js"; -import logger from "../utils/logger.js"; -import jwt from "jsonwebtoken"; -import { handleError, handleValidationError } from "./controllerUtils.js"; -import { getTokenFromHeaders } from "../utils/utils.js"; - -const SERVICE_NAME = "inviteController"; - -class InviteController { - constructor(db, settingsService, emailService, stringService) { - this.db = db; - this.settingsService = settingsService; - this.emailService = emailService; - this.stringService = stringService; - } - - /** - * Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @property {Object} req.body - The body of the request. - * @property {string} req.body.email - The email of the user to be invited. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the sending of the invitation, and the invitation token. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - issueInvitation = async (req, res, next) => { - try { - // Only admins can invite - const token = getTokenFromHeaders(req.headers); - const { role, firstname, teamId } = jwt.decode(token); - req.body.teamId = teamId; - try { - await inviteRoleValidation.validateAsync({ roles: role }); - await inviteBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - const inviteToken = await this.db.requestInviteToken({ ...req.body }); - const { clientHost } = this.settingsService.getSettings(); - this.emailService - .buildAndSendEmail( - "employeeActivationTemplate", - { - name: firstname, - link: `${clientHost}/register/${inviteToken.token}`, - }, - req.body.email, - "Welcome to Uptime Monitor" - ) - .catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "issueInvitation", - stack: error.stack, - }); - }); - - return res.success({ - msg: this.stringService.inviteIssued, - data: inviteToken, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteController")); - } - }; - - inviteVerifyController = async (req, res, next) => { - try { - await inviteVerificationBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const invite = await this.db.getInviteToken(req.body.token); - - return res.success({ - msg: this.stringService.inviteVerified, - data: invite, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteVerifyController")); - } - }; -} - -export default InviteController; diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js deleted file mode 100644 index a11f957ed..000000000 --- a/Server/controllers/maintenanceWindowController.js +++ /dev/null @@ -1,163 +0,0 @@ -import { - createMaintenanceWindowBodyValidation, - editMaintenanceWindowByIdParamValidation, - editMaintenanceByIdWindowBodyValidation, - getMaintenanceWindowByIdParamValidation, - getMaintenanceWindowsByMonitorIdParamValidation, - getMaintenanceWindowsByTeamIdQueryValidation, - deleteMaintenanceWindowByIdParamValidation, -} from "../validation/joi.js"; -import jwt from "jsonwebtoken"; -import { getTokenFromHeaders } from "../utils/utils.js"; -import { handleValidationError, handleError } from "./controllerUtils.js"; - -const SERVICE_NAME = "maintenanceWindowController"; - -class MaintenanceWindowController { - constructor(db, settingsService, stringService) { - this.db = db; - this.settingsService = settingsService; - this.stringService = stringService; - } - - createMaintenanceWindows = async (req, res, next) => { - try { - await createMaintenanceWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const monitorIds = req.body.monitors; - const dbTransactions = monitorIds.map((monitorId) => { - return this.db.createMaintenanceWindow({ - teamId, - monitorId, - name: req.body.name, - active: req.body.active ? req.body.active : true, - repeat: req.body.repeat, - start: req.body.start, - end: req.body.end, - }); - }); - await Promise.all(dbTransactions); - - return res.success({ - msg: this.stringService.maintenanceWindowCreate, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createMaintenanceWindow")); - } - }; - - getMaintenanceWindowById = async (req, res, next) => { - try { - await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const maintenanceWindow = await this.db.getMaintenanceWindowById(req.params.id); - - return res.success({ - msg: this.stringService.maintenanceWindowGetById, - data: maintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById")); - } - }; - - getMaintenanceWindowsByTeamId = async (req, res, next) => { - try { - await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId( - teamId, - req.query - ); - - return res.success({ - msg: this.stringService.maintenanceWindowGetByTeam, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId")); - } - }; - - getMaintenanceWindowsByMonitorId = async (req, res, next) => { - try { - await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId( - req.params.monitorId - ); - - return res.success({ - msg: this.stringService.maintenanceWindowGetByUser, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId")); - } - }; - - deleteMaintenanceWindow = async (req, res, next) => { - try { - await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - await this.db.deleteMaintenanceWindowById(req.params.id); - return res.success({ - msg: this.stringService.maintenanceWindowDelete, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow")); - } - }; - - editMaintenanceWindow = async (req, res, next) => { - try { - await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); - await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const editedMaintenanceWindow = await this.db.editMaintenanceWindowById( - req.params.id, - req.body - ); - return res.success({ - msg: this.stringService.maintenanceWindowEdit, - data: editedMaintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "editMaintenanceWindow")); - } - }; -} - -export default MaintenanceWindowController; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js deleted file mode 100644 index 9e87e53e5..000000000 --- a/Server/controllers/monitorController.js +++ /dev/null @@ -1,548 +0,0 @@ -import { - getMonitorByIdParamValidation, - getMonitorByIdQueryValidation, - getMonitorsByTeamIdParamValidation, - getMonitorsByTeamIdQueryValidation, - createMonitorBodyValidation, - getMonitorURLByQueryValidation, - editMonitorBodyValidation, - pauseMonitorParamValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, - getCertificateParamValidation, - getHardwareDetailsByIdParamValidation, - getHardwareDetailsByIdQueryValidation, -} from "../validation/joi.js"; -import sslChecker from "ssl-checker"; -import jwt from "jsonwebtoken"; -import { getTokenFromHeaders } from "../utils/utils.js"; -import logger from "../utils/logger.js"; -import { handleError, handleValidationError } from "./controllerUtils.js"; -import axios from "axios"; -import seedDb from "../db/mongo/utils/seedDb.js"; -const SERVICE_NAME = "monitorController"; - -class MonitorController { - constructor(db, settingsService, jobQueue, stringService) { - this.db = db; - this.settingsService = settingsService; - this.jobQueue = jobQueue; - this.stringService = stringService; - } - - /** - * Returns all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ - getAllMonitors = async (req, res, next) => { - try { - const monitors = await this.db.getAllMonitors(); - return res.success({ - msg: this.stringService.monitorGetAll, - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllMonitors")); - } - }; - - /** - * Returns all monitors with uptime stats for 1,7,30, and 90 days - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ - getAllMonitorsWithUptimeStats = async (req, res, next) => { - try { - const monitors = await this.db.getAllMonitorsWithUptimeStats(); - return res.success({ - msg: this.stringService.monitorGetAll, - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAllMonitorsWithUptimeStats")); - } - }; - - getUptimeDetailsById = async (req, res, next) => { - try { - const monitor = await this.db.getUptimeDetailsById(req); - return res.success({ - msg: this.stringService.monitorGetById, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorDetailsById")); - } - }; - - /** - * Returns monitor stats for monitor with matching ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @param {function} next - * @returns {Promise} - * @throws {Error} - */ - getMonitorStatsById = async (req, res, next) => { - try { - await getMonitorStatsByIdParamValidation.validateAsync(req.params); - await getMonitorStatsByIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const monitorStats = await this.db.getMonitorStatsById(req); - return res.success({ - msg: this.stringService.monitorStatsById, - data: monitorStats, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorStatsById")); - } - }; - - /** - * Get hardware details for a specific monitor by ID - * @async - * @param {Express.Request} req - Express request object containing monitorId in params - * @param {Express.Response} res - Express response object - * @param {Express.NextFunction} next - Express next middleware function - * @returns {Promise} - * @throws {Error} - Throws error if monitor not found or other database errors - */ - getHardwareDetailsById = async (req, res, next) => { - try { - await getHardwareDetailsByIdParamValidation.validateAsync(req.params); - await getHardwareDetailsByIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const monitor = await this.db.getHardwareDetailsById(req); - return res.success({ - msg: this.stringService.monitorGetById, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getHardwareDetailsById")); - } - }; - - getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => { - try { - await getCertificateParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - } - - try { - const { monitorId } = req.params; - const monitor = await this.db.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(sslChecker, monitor); - - return res.success({ - msg: this.stringService.monitorCertificate, - data: { - certificateDate: new Date(certificate.validTo), - }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorCertificate")); - } - }; - - /** - * Retrieves a monitor by its ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be retrieved. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the retrieved monitor data. - * @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422). - */ - getMonitorById = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - await getMonitorByIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const monitor = await this.db.getMonitorById(req.params.monitorId); - return res.success({ - msg: this.stringService.monitorGetById, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorById")); - } - }; - - /** - * Creates a new monitor and adds it to the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the creation of the monitor, and the created monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - createMonitor = async (req, res, next) => { - try { - await createMonitorBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const notifications = req.body.notifications; - const monitor = await this.db.createMonitor(req, res); - - if (notifications && notifications.length > 0) { - monitor.notifications = await Promise.all( - notifications.map(async (notification) => { - notification.monitorId = monitor._id; - return await this.db.createNotification(notification); - }) - ); - } - - await monitor.save(); - // Add monitor to job queue - this.jobQueue.addJob(monitor._id, monitor); - return res.success({ - msg: this.stringService.monitorCreate, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createMonitor")); - } - }; - - /** - * Checks if the endpoint can be resolved - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the resolution result. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - checkEndpointResolution = async (req, res, next) => { - try { - await getMonitorURLByQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { monitorURL } = req.query; - const parsedUrl = new URL(monitorURL); - const response = await axios.get(parsedUrl, { - timeout: 5000, - validateStatus: () => true, - }); - return res.success({ - status: response.status, - msg: response.statusText, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "checkEndpointResolution")); - } - }; - - /** - * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be deleted. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status and a message indicating the deletion of the monitor. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records. - */ - deleteMonitor = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const monitor = await this.db.deleteMonitor(req, res, next); - // Delete associated checks,alerts,and notifications - - try { - const operations = [ - { name: "deleteJob", fn: () => this.jobQueue.deleteJob(monitor) }, - { name: "deleteChecks", fn: () => this.db.deleteChecks(monitor._id) }, - { - name: "deletePageSpeedChecks", - fn: () => this.db.deletePageSpeedChecksByMonitorId(monitor._id), - }, - { - name: "deleteNotifications", - fn: () => this.db.deleteNotificationsByMonitorId(monitor._id), - }, - { - name: "deleteHardwareChecks", - fn: () => this.db.deleteHardwareChecksByMonitorId(monitor._id), - }, - { - name: "deleteDistributedUptimeChecks", - fn: () => this.db.deleteDistributedChecksByMonitorId(monitor._id), - }, - - // TODO We don't actually want to delete the status page if there are other monitors in it - // We actually just want to remove the monitor being deleted from the status page. - // Only delete he status page if there are no other monitors in it. - { - name: "deleteStatusPages", - fn: () => this.db.deleteStatusPagesByMonitorId(monitor._id), - }, - ]; - const results = await Promise.allSettled(operations.map((op) => op.fn())); - - results.forEach((result, index) => { - if (result.status === "rejected") { - const operationName = operations[index].name; - logger.error({ - message: `Failed to ${operationName} for monitor ${monitor._id}`, - service: SERVICE_NAME, - method: "deleteMonitor", - stack: result.reason.stack, - }); - } - }); - } catch (error) { - logger.error({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteMonitor", - stack: error.stack, - }); - } - return res.success({ msg: this.stringService.monitorDelete }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteMonitor")); - } - }; - - /** - * Deletes all monitors associated with a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - * @returns {Object} The response object with a success status and a message indicating the number of deleted monitors. - * @throws {Error} If there is an error during the deletion process. - */ - deleteAllMonitors = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); - await Promise.all( - monitors.map(async (monitor) => { - try { - await this.jobQueue.deleteJob(monitor); - await this.db.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); - } catch (error) { - logger.error({ - message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, - service: SERVICE_NAME, - method: "deleteAllMonitors", - stack: error.stack, - }); - } - }) - ); - return res.success({ msg: `Deleted ${deletedCount} monitors` }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteAllMonitors")); - } - }; - - /** - * Edits a monitor by its ID, updates its notifications, and updates its job in the job queue. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be edited. - * @property {Object} req.body - The body of the request. - * @property {Array} req.body.notifications - The notifications to be associated with the monitor. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the editing of the monitor, and the edited monitor data. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - editMonitor = async (req, res, next) => { - try { - await getMonitorByIdParamValidation.validateAsync(req.params); - await editMonitorBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { monitorId } = req.params; - const monitorBeforeEdit = await this.db.getMonitorById(monitorId); - - // Get notifications from the request body - const notifications = req.body.notifications; - - const editedMonitor = await this.db.editMonitor(monitorId, req.body); - - await this.db.deleteNotificationsByMonitorId(editedMonitor._id); - - await Promise.all( - notifications && - notifications.map(async (notification) => { - notification.monitorId = editedMonitor._id; - await this.db.createNotification(notification); - }) - ); - - // Delete the old job(editedMonitor has the same ID as the old monitor) - await this.jobQueue.deleteJob(monitorBeforeEdit); - // Add the new job back to the queue - await this.jobQueue.addJob(editedMonitor._id, editedMonitor); - return res.success({ - msg: this.stringService.monitorEdit, - data: editedMonitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "editMonitor")); - } - }; - - /** - * Pauses or resumes a monitor based on its current state. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.monitorId - The ID of the monitor to be paused or resumed. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the new state of the monitor, and the updated monitor data. - * @throws {Error} If there is an error during the process. - */ - pauseMonitor = async (req, res, next) => { - try { - await pauseMonitorParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - } - - try { - const monitor = await this.db.getMonitorById(req.params.monitorId); - monitor.isActive === true - ? await this.jobQueue.deleteJob(monitor) - : await this.jobQueue.addJob(monitor._id, monitor); - - monitor.isActive = !monitor.isActive; - monitor.status = undefined; - monitor.save(); - return res.success({ - msg: monitor.isActive - ? this.stringService.monitorResume - : this.stringService.monitorPause, - data: monitor, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "pauseMonitor")); - } - }; - - /** - * Adds demo monitors for a team. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.headers - The headers of the request. - * @property {string} req.headers.authorization - The authorization header containing the JWT token. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message indicating the addition of demo monitors, and the number of demo monitors added. - * @throws {Error} If there is an error during the process. - */ - addDemoMonitors = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { _id, teamId } = jwt.verify(token, jwtSecret); - const demoMonitors = await this.db.addDemoMonitors(_id, teamId); - await Promise.all( - demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor)) - ); - - return res.success({ - msg: this.stringService.monitorDemoAdded, - data: demoMonitors.length, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "addDemoMonitors")); - } - }; - - getMonitorsByTeamId = async (req, res, next) => { - try { - await getMonitorsByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - } - - try { - const monitors = await this.db.getMonitorsByTeamId(req); - return res.success({ - msg: this.stringService.monitorGetByTeamId, - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); - } - }; - - seedDb = async (req, res, next) => { - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = this.settingsService.getSettings(); - const { _id, teamId } = jwt.verify(token, jwtSecret); - await seedDb(_id, teamId); - } catch (error) { - next(handleError(error, SERVICE_NAME, "seedDb")); - } - }; -} - -export default MonitorController; diff --git a/Server/controllers/notificationController.js b/Server/controllers/notificationController.js deleted file mode 100644 index bce20ea13..000000000 --- a/Server/controllers/notificationController.js +++ /dev/null @@ -1,59 +0,0 @@ -import { - triggerNotificationBodyValidation, -} from '../validation/joi.js'; -import { handleError, handleValidationError } from './controllerUtils.js'; - -const SERVICE_NAME = "NotificationController"; - -class NotificationController { - constructor(notificationService, stringService) { - this.notificationService = notificationService; - this.stringService = stringService; - this.triggerNotification = this.triggerNotification.bind(this); - } - - async triggerNotification(req, res, next) { - try { - await triggerNotificationBodyValidation.validateAsync(req.body, { - abortEarly: false, - stripUnknown: true - }); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { monitorId, type, platform, config } = req.body; - - const networkResponse = { - monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, - status: false, - statusChanged: true, - prevStatus: true, - }; - - if (type === "webhook") { - const notification = { - type, - platform, - config - }; - - await this.notificationService.sendWebhookNotification( - networkResponse, - notification - ); - } - - return res.success({ - msg: this.stringService.webhookSendSuccess - }); - - } catch (error) { - next(handleError(error, SERVICE_NAME, "triggerNotification")); - } - } -} - -export default NotificationController; \ No newline at end of file diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js deleted file mode 100644 index 330ade532..000000000 --- a/Server/controllers/queueController.js +++ /dev/null @@ -1,61 +0,0 @@ -import { handleError } from "./controllerUtils.js"; - -const SERVICE_NAME = "JobQueueController"; - -class JobQueueController { - constructor(jobQueue, stringService) { - this.jobQueue = jobQueue; - this.stringService = stringService; - } - - getMetrics = async (req, res, next) => { - try { - const metrics = await this.jobQueue.getMetrics(); - res.success({ - msg: this.stringService.queueGetMetrics, - data: metrics, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMetrics")); - return; - } - }; - - getJobs = async (req, res, next) => { - try { - const jobs = await this.jobQueue.getJobStats(); - return res.success({ - msg: this.stringService.queueGetMetrics, - data: jobs, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getJobs")); - return; - } - }; - - addJob = async (req, res, next) => { - try { - await this.jobQueue.addJob(Math.random().toString(36).substring(7)); - return res.success({ - msg: this.stringService.queueAddJob, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "addJob")); - return; - } - }; - - obliterateQueue = async (req, res, next) => { - try { - await this.jobQueue.obliterate(); - return res.success({ - msg: this.stringService.queueObliterate, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "obliterateQueue")); - return; - } - }; -} -export default JobQueueController; diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js deleted file mode 100644 index 54643dfbb..000000000 --- a/Server/controllers/settingsController.js +++ /dev/null @@ -1,48 +0,0 @@ -import { updateAppSettingsBodyValidation } from "../validation/joi.js"; -import { handleValidationError, handleError } from "./controllerUtils.js"; - -const SERVICE_NAME = "SettingsController"; - -class SettingsController { - constructor(db, settingsService, stringService) { - this.db = db; - this.settingsService = settingsService; - this.stringService = stringService; - } - - getAppSettings = async (req, res, next) => { - try { - const settings = { ...(await this.settingsService.getSettings()) }; - delete settings.jwtSecret; - return res.success({ - msg: this.stringService.getAppSettings, - data: settings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAppSettings")); - } - }; - - updateAppSettings = async (req, res, next) => { - try { - await updateAppSettingsBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - await this.db.updateAppSettings(req.body); - const updatedSettings = { ...(await this.settingsService.reloadSettings()) }; - delete updatedSettings.jwtSecret; - return res.success({ - msg: this.stringService.updateAppSettings, - data: updatedSettings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateAppSettings")); - } - }; -} - -export default SettingsController; diff --git a/Server/controllers/statusPageController.js b/Server/controllers/statusPageController.js deleted file mode 100644 index eb3e69484..000000000 --- a/Server/controllers/statusPageController.js +++ /dev/null @@ -1,120 +0,0 @@ -import { handleError, handleValidationError } from "./controllerUtils.js"; -import { - createStatusPageBodyValidation, - getStatusPageParamValidation, - getStatusPageQueryValidation, - imageValidation, -} from "../validation/joi.js"; - -const SERVICE_NAME = "statusPageController"; - -class StatusPageController { - constructor(db, stringService) { - this.db = db; - this.stringService = stringService; - } - - createStatusPage = async (req, res, next) => { - try { - await createStatusPageBodyValidation.validateAsync(req.body); - await imageValidation.validateAsync(req.file); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const statusPage = await this.db.createStatusPage(req.body, req.file); - return res.success({ - msg: this.stringService.statusPageCreate, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createStatusPage")); - } - }; - - updateStatusPage = async (req, res, next) => { - try { - await createStatusPageBodyValidation.validateAsync(req.body); - await imageValidation.validateAsync(req.file); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - 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; - } - return res.success({ - msg: this.stringService.statusPageUpdate, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateStatusPage")); - } - }; - - getStatusPage = async (req, res, next) => { - try { - const statusPage = await this.db.getStatusPage(); - return res.success({ - msg: this.stringService.statusPageByUrl, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getStatusPage")); - } - }; - - getStatusPageByUrl = async (req, res, next) => { - try { - await getStatusPageParamValidation.validateAsync(req.params); - await getStatusPageQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type); - return res.success({ - msg: this.stringService.statusPageByUrl, - data: statusPage, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getStatusPageByUrl")); - } - }; - - getStatusPagesByTeamId = async (req, res, next) => { - try { - const teamId = req.params.teamId; - const statusPages = await this.db.getStatusPagesByTeamId(teamId); - - return res.success({ - msg: this.stringService.statusPageByTeamId, - data: statusPages, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getStatusPageByTeamId")); - } - }; - - deleteStatusPage = async (req, res, next) => { - try { - await this.db.deleteStatusPage(req.params.url); - return res.success({ - msg: this.stringService.statusPageDelete, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteStatusPage")); - } - }; -} - -export default StatusPageController; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js deleted file mode 100644 index 4e9b32992..000000000 --- a/Server/db/FakeDb.js +++ /dev/null @@ -1,138 +0,0 @@ -// ************************** -// The idea here is to provide a layer of abstraction between the database and whoever is using it. -// Instead of directly calling mongoose methods, we can call the methods on the DB object. -// If this were Typescript or Java or Golang an interface would be implemented to ensure the methods are available. -// But we do the best we can with Javascript. -// -// If the methods are consistent all we have to do to swap out one DB for another is simply change the import. -// -// Example: -// We start with the fake DB: -// -// const db = require("../db/FakeDb"); -// const monitors = await db.getAllMonitors(); -// -// And when we want to swtich to a real DB, all we have to do is swap the import -// -// const db = require("../db/MongoDb"); -// const monitors = await db.getAllMonitors(); -// -// The rest of the code is the same, as all the `db` methods are standardized. -// ************************** - -const Monitor = require("./models/Monitor"); -const UserModel = require("./models/User"); -const bcrypt = require("bcrypt"); - -let FAKE_MONITOR_DATA = []; -const USERS = []; - -const connect = async () => { - try { - await console.log("Connected to FakeDB"); - } catch (error) { - console.error(error); - } -}; - -const insertUser = async (req, res) => { - try { - const newUser = new UserModel({ ...req.body }); - const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait - newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync - USERS.push(newUser); - const userToReturn = { ...newUser._doc }; - delete userToReturn.password; - return userToReturn; - } catch (error) { - throw error; - } -}; - -const getUserByEmail = async (req, res) => { - const email = req.body.email; - try { - const idx = USERS.findIndex((user) => { - return user.email === email; - }); - if (idx === -1) { - return null; - } - return USERS[idx]; - } catch (error) { - throw new Error(`User with email ${email} not found`); - } -}; - -const getAllMonitors = async () => { - return FAKE_MONITOR_DATA; -}; - -const getMonitorById = async (monitorId) => { - const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { - return monitor.id === monitorId; - }); - if (idx === -1) { - throw new Error(`Monitor with id ${monitorId} not found`); - } - return FAKE_MONITOR_DATA[idx]; -}; - -const getMonitorsByUserId = async (userId) => { - const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => { - return monitor.userId === userId; - }); - - if (userMonitors.length === 0) { - throw new Error(`Monitors for user ${userId} not found`); - } - return userMonitors; -}; - -const createMonitor = async (req, res) => { - const monitor = new Monitor(req.body); - monitor.createdAt = Date.now(); - monitor.updatedAt = Date.now(); - FAKE_MONITOR_DATA.push(monitor); - return monitor; -}; - -const deleteMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - try { - const monitor = getMonitorById(monitorId); - FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => { - return monitor.id !== monitorId; - }); - return monitor; - } catch (error) { - throw error; - } -}; - -const editMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { - return monitor._id.toString() === monitorId; - }); - const oldMonitor = FAKE_MONITOR_DATA[idx]; - const editedMonitor = new Monitor({ ...req.body }); - editedMonitor._id = oldMonitor._id; - editedMonitor.userId = oldMonitor.userId; - editedMonitor.updatedAt = Date.now(); - editedMonitor.createdAt = oldMonitor.createdAt; - FAKE_MONITOR_DATA[idx] = editedMonitor; - return FAKE_MONITOR_DATA[idx]; -}; - -module.exports = { - connect, - insertUser, - getUserByEmail, - getAllMonitors, - getMonitorById, - getMonitorsByUserId, - createMonitor, - deleteMonitor, - editMonitor, -}; diff --git a/Server/db/models/AppSettings.js b/Server/db/models/AppSettings.js deleted file mode 100644 index c4faa0881..000000000 --- a/Server/db/models/AppSettings.js +++ /dev/null @@ -1,91 +0,0 @@ -import mongoose from "mongoose"; - -const AppSettingsSchema = mongoose.Schema( - { - apiBaseUrl: { - type: String, - required: true, - default: "http://localhost:5000/api/v1", - }, - logLevel: { - type: String, - default: "debug", - enum: ["debug", "none", "error", "warn"], - }, - clientHost: { - type: String, - required: true, - default: "http://localhost:5173", - }, - jwtSecret: { - type: String, - required: true, - default: "my_secret", - }, - refreshTokenSecret: { - type: String, - required: true, - default: "my_refresh_secret", - }, - dbType: { - type: String, - required: true, - default: "MongoDB", - }, - dbConnectionString: { - type: String, - required: true, - default: "mongodb://localhost:27017/uptime_db", - }, - redisHost: { - type: String, - required: true, - default: "127.0.0.1", - }, - redisPort: { - type: Number, - default: "6379", - }, - jwtTTL: { - type: String, - required: true, - default: "2h", - }, - refreshTokenTTL: { - type: String, - required: true, - default: "7d", - }, - pagespeedApiKey: { - type: String, - default: "", - }, - systemEmailHost: { - type: String, - default: "smtp.gmail.com", - }, - systemEmailPort: { - type: Number, - default: 465, - }, - systemEmailAddress: { - type: String, - default: "", - }, - systemEmailPassword: { - type: String, - default: "", - }, - singleton: { - type: Boolean, - required: true, - unique: true, - default: true, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("AppSettings", AppSettingsSchema); diff --git a/Server/db/models/Check.js b/Server/db/models/Check.js deleted file mode 100644 index 158749986..000000000 --- a/Server/db/models/Check.js +++ /dev/null @@ -1,83 +0,0 @@ -import mongoose from "mongoose"; - -const BaseCheckSchema = mongoose.Schema({ - /** - * Reference to the associated Monitor document. - * - * @type {mongoose.Schema.Types.ObjectId} - */ - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - index: true, - }, - /** - * Status of the check (true for up, false for down). - * - * @type {Boolean} - */ - status: { - type: Boolean, - index: true, - }, - /** - * Response time of the check in milliseconds. - * - * @type {Number} - */ - responseTime: { - type: Number, - }, - /** - * HTTP status code received during the check. - * - * @type {Number} - */ - statusCode: { - type: Number, - index: true, - }, - /** - * Message or description of the check result. - * - * @type {String} - */ - message: { - type: String, - }, - /** - * Expiry date of the check, auto-calculated to expire after 30 days. - * - * @type {Date} - */ - - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, -}); - -/** - * Check Schema for MongoDB collection. - * - * Represents a check associated with a monitor, storing information - * about the status and response of a particular check event. - */ -const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true }); -CheckSchema.index({ createdAt: 1 }); -CheckSchema.index({ monitorId: 1, createdAt: 1 }); -CheckSchema.index({ monitorId: 1, createdAt: -1 }); -CheckSchema.index({ teamId: 1, createdAt: -1 }); -CheckSchema.index({ teamId: 1 }); - -export default mongoose.model("Check", CheckSchema); -export { BaseCheckSchema }; diff --git a/Server/db/models/DistributedUptimeCheck.js b/Server/db/models/DistributedUptimeCheck.js deleted file mode 100644 index adbeffd09..000000000 --- a/Server/db/models/DistributedUptimeCheck.js +++ /dev/null @@ -1,123 +0,0 @@ -import mongoose from "mongoose"; - -import { BaseCheckSchema } from "./Check.js"; - -// { -// "id": "12123", -// "result": { -// "task_arrived": "2025-01-13T19:21:37.463466602Z", -// "dns_start": "2025-01-14T00:21:33.1801319+05:00", -// "dns_end": "2025-01-14T00:21:33.4582552+05:00", -// "conn_start": "2025-01-14T00:21:33.1801319+05:00", -// "conn_end": "2025-01-14T00:21:33.7076318+05:00", -// "connect_start": "2025-01-14T00:21:33.4582552+05:00", -// "connect_end": "2025-01-14T00:21:33.541899+05:00", -// "tls_hand_shake_start": "2025-01-14T00:21:33.541899+05:00", -// "tls_hand_shake_end": "2025-01-14T00:21:33.7076318+05:00", -// "body_read_start": "2025-01-14T00:21:34.1894707+05:00", -// "body_read_end": "2025-01-14T00:21:34.1894707+05:00", -// "wrote_request": "2025-01-14T00:21:33.7076318+05:00", -// "got_first_response_byte": "2025-01-14T00:21:34.1327652+05:00", -// "first_byte_took": 425133400, -// "body_read_took": 56030000, -// "dns_took": 278123300, -// "conn_took": 527499900, -// "connect_took": 83643800, -// "tls_took": 165732800, -// "sni_name": "uprock.com", -// "status_code": 200, -// "body_size": 19320, -// "request_header_size": 95, -// "response_header_size": 246, -// "response_headers": "X-Vercel-Id: bom1::iad1::sm87v-1736796096856-aec270c01f23\nDate: Mon, 13 Jan 2025 19:21:37 GMT\nServer: Vercel\nStrict-Transport-Security: max-age=63072000\nVary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url\nX-Matched-Path: /\nX-Powered-By: Next.js\nX-Vercel-Cache: MISS\nAge: 0\nCache-Control: private, no-cache, no-store, max-age=0, must-revalidate\nContent-Type: text/html; charset=utf-8", -// "error": "", -// "device_id": "d5f578e143a2cd603dd6bf5f846a86a538bde4a8fbe2ad1fca284ad9f033daf8", -// "ip_address": "223.123.19.0", -// "proof": "", -// "created_at": "2025-01-13T19:21:37.463466912Z", -// "continent": "AS", -// "country_code": "PK", -// "city": "Muzaffargarh", -// "upt_burnt" : "0.01", -// "location": { -// "lat": 71.0968, -// "lng": 30.0208 -// }, -// "payload": { -// "callback": "https://webhook.site/2a15b0af-545a-4ac2-b913-153b97592d7a", -// "x": "y" -// } -// } -// } - -const LocationSchema = new mongoose.Schema( - { - lat: { type: Number, required: true }, - lng: { type: Number, required: true }, - }, - { _id: false } -); - -const DistributedUptimeCheckSchema = mongoose.Schema( - { - ...BaseCheckSchema.obj, - first_byte_took: { - type: Number, - required: false, - }, - body_read_took: { - type: Number, - required: false, - }, - dns_took: { - type: Number, - required: false, - }, - conn_took: { - type: Number, - required: false, - }, - connect_took: { - type: Number, - required: false, - }, - tls_took: { - type: Number, - required: false, - }, - location: { - type: LocationSchema, - required: false, - }, - continent: { - type: String, - required: false, - }, - countryCode: { - type: String, - required: false, - }, - city: { - type: String, - required: false, - }, - uptBurnt: { - type: mongoose.Schema.Types.Decimal128, - required: false, - }, - }, - { timestamps: true } -); - -DistributedUptimeCheckSchema.pre("save", function (next) { - if (this.isModified("uptBurnt") && typeof this.uptBurnt === "string") { - this.uptBurnt = mongoose.Types.Decimal128.fromString(this.uptBurnt); - } - next(); -}); - -DistributedUptimeCheckSchema.index({ createdAt: 1 }); -DistributedUptimeCheckSchema.index({ monitorId: 1, createdAt: 1 }); -DistributedUptimeCheckSchema.index({ monitorId: 1, createdAt: -1 }); - -export default mongoose.model("DistributedUptimeCheck", DistributedUptimeCheckSchema); diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js deleted file mode 100644 index 76b9dfc2e..000000000 --- a/Server/db/models/HardwareCheck.js +++ /dev/null @@ -1,70 +0,0 @@ -import mongoose from "mongoose"; -import { BaseCheckSchema } from "./Check.js"; -const cpuSchema = mongoose.Schema({ - physical_core: { type: Number, default: 0 }, - logical_core: { type: Number, default: 0 }, - frequency: { type: Number, default: 0 }, - temperature: { type: [Number], default: [] }, - free_percent: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const memorySchema = mongoose.Schema({ - total_bytes: { type: Number, default: 0 }, - available_bytes: { type: Number, default: 0 }, - used_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const diskSchema = mongoose.Schema({ - read_speed_bytes: { type: Number, default: 0 }, - write_speed_bytes: { type: Number, default: 0 }, - total_bytes: { type: Number, default: 0 }, - free_bytes: { type: Number, default: 0 }, - usage_percent: { type: Number, default: 0 }, -}); - -const hostSchema = mongoose.Schema({ - os: { type: String, default: "" }, - platform: { type: String, default: "" }, - kernel_version: { type: String, default: "" }, -}); - -const errorSchema = mongoose.Schema({ - metric: { type: [String], default: [] }, - err: { type: String, default: "" }, -}); - -const HardwareCheckSchema = mongoose.Schema( - { - ...BaseCheckSchema.obj, - cpu: { - type: cpuSchema, - default: () => ({}), - }, - memory: { - type: memorySchema, - default: () => ({}), - }, - disk: { - type: [diskSchema], - default: () => [], - }, - host: { - type: hostSchema, - default: () => ({}), - }, - - errors: { - type: [errorSchema], - default: () => [], - }, - }, - { timestamps: true } -); - -HardwareCheckSchema.index({ createdAt: 1 }); -HardwareCheckSchema.index({ monitorId: 1, createdAt: 1 }); -HardwareCheckSchema.index({ monitorId: 1, createdAt: -1 }); - -export default mongoose.model("HardwareCheck", HardwareCheckSchema); diff --git a/Server/db/models/InviteToken.js b/Server/db/models/InviteToken.js deleted file mode 100644 index 0c2402b07..000000000 --- a/Server/db/models/InviteToken.js +++ /dev/null @@ -1,34 +0,0 @@ -import mongoose from "mongoose"; -const InviteTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: Array, - required: true, - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/Server/db/models/MaintenanceWindow.js b/Server/db/models/MaintenanceWindow.js deleted file mode 100644 index b68abb413..000000000 --- a/Server/db/models/MaintenanceWindow.js +++ /dev/null @@ -1,68 +0,0 @@ -import mongoose from "mongoose"; -/** - * MaintenanceWindow Schema - * @module MaintenanceWindow - * @typedef {Object} MaintenanceWindow - * @property {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. This is a reference to the Monitor model and is immutable. - * @property {Boolean} active - Indicates whether the maintenance window is active. - * @property {Number} repeat - Indicates how often this maintenance window should repeat. - * @property {Date} start - The start date and time of the maintenance window. - * @property {Date} end - The end date and time of the maintenance window. - * @property {Date} expiry - The expiry date and time of the maintenance window. This is used for MongoDB's TTL index to automatically delete the document at this time. This field is set to the same value as `end` when `oneTime` is `true`. - * - * @example - * - * let maintenanceWindow = new MaintenanceWindow({ - * monitorId: monitorId, - * active: active, - * repeat: repeat, - * start: start, - * end: end, - * }); - * - * if (repeat === 0) { - * maintenanceWindow.expiry = end; - * } - * - */ - -const MaintenanceWindow = mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - }, - active: { - type: Boolean, - default: true, - }, - name: { - type: String, - }, - repeat: { - type: Number, - }, - start: { - type: Date, - }, - end: { - type: Date, - }, - expiry: { - type: Date, - index: { expires: "0s" }, - }, - }, - - { - timestamps: true, - } -); - -export default mongoose.model("MaintenanceWindow", MaintenanceWindow); diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js deleted file mode 100644 index eb5f67c1b..000000000 --- a/Server/db/models/Monitor.js +++ /dev/null @@ -1,98 +0,0 @@ -import mongoose from "mongoose"; - -const MonitorSchema = mongoose.Schema( - { - userId: { - type: mongoose.Schema.Types.ObjectId, - ref: "User", - immutable: true, - required: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - name: { - type: String, - required: true, - }, - description: { - type: String, - }, - status: { - type: Boolean, - default: undefined, - }, - type: { - type: String, - required: true, - enum: [ - "http", - "ping", - "pagespeed", - "hardware", - "docker", - "port", - "distributed_http", - ], - }, - jsonPath: { - type: String, - }, - expectedValue: { - type: String, - }, - matchMethod: { - type: String, - enum: [ - "equal", - "include", - "regex", - ], - }, - url: { - type: String, - required: true, - }, - port: { - type: Number, - }, - isActive: { - type: Boolean, - default: true, - }, - interval: { - // in milliseconds - type: Number, - default: 60000, - }, - uptimePercentage: { - type: Number, - default: undefined, - }, - thresholds: { - type: { - usage_cpu: { type: Number }, - usage_memory: { type: Number }, - usage_disk: { type: Number }, - }, - _id: false, - }, - notifications: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Notification", - }, - ], - secret: { - type: String, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("Monitor", MonitorSchema); diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js deleted file mode 100644 index 53d9eb4a2..000000000 --- a/Server/db/models/Notification.js +++ /dev/null @@ -1,91 +0,0 @@ -import mongoose from "mongoose"; - -const configSchema = mongoose.Schema({ - webhookUrl: { type: String }, - botToken: { type: String }, - chatId: { type: String } -}, { _id: false }); - -const NotificationSchema = mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - type: { - type: String, - enum: ["email", "sms", "webhook"], - }, - config: { - type: configSchema, - default: () => ({}) - }, - address: { - type: String, - }, - phone: { - type: String, - }, - alertThreshold: { - type: Number, - default: 5, - }, - cpuAlertThreshold: { - type: Number, - default: function () { - return this.alertThreshold; - }, - }, - memoryAlertThreshold: { - type: Number, - default: function () { - return this.alertThreshold; - }, - }, - diskAlertThreshold: { - type: Number, - default: function () { - return this.alertThreshold; - }, - }, - tempAlertThreshold: { - type: Number, - default: function () { - return this.alertThreshold; - }, - }, - }, - { - timestamps: true, - } -); - -NotificationSchema.pre("save", function (next) { - if (!this.cpuAlertThreshold || this.isModified("alertThreshold")) { - this.cpuAlertThreshold = this.alertThreshold; - } - if (!this.memoryAlertThreshold || this.isModified("alertThreshold")) { - this.memoryAlertThreshold = this.alertThreshold; - } - if (!this.diskAlertThreshold || this.isModified("alertThreshold")) { - this.diskAlertThreshold = this.alertThreshold; - } - if (!this.tempAlertThreshold || this.isModified("alertThreshold")) { - this.tempAlertThreshold = this.alertThreshold; - } - next(); -}); - -NotificationSchema.pre("findOneAndUpdate", function (next) { - const update = this.getUpdate(); - if (update.alertThreshold) { - update.cpuAlertThreshold = update.alertThreshold; - update.memoryAlertThreshold = update.alertThreshold; - update.diskAlertThreshold = update.alertThreshold; - update.tempAlertThreshold = update.alertThreshold; - } - next(); -}); - -export default mongoose.model("Notification", NotificationSchema); diff --git a/Server/db/models/PageSpeedCheck.js b/Server/db/models/PageSpeedCheck.js deleted file mode 100644 index c04df0931..000000000 --- a/Server/db/models/PageSpeedCheck.js +++ /dev/null @@ -1,111 +0,0 @@ -import mongoose from "mongoose"; -import { BaseCheckSchema } from "./Check.js"; -import logger from "../../utils/logger.js"; -import { time } from "console"; -const AuditSchema = mongoose.Schema({ - id: { type: String, required: true }, - title: { type: String, required: true }, - description: { type: String, required: true }, - score: { type: Number, required: true }, - scoreDisplayMode: { type: String, required: true }, - displayValue: { type: String, required: true }, - numericValue: { type: Number, required: true }, - numericUnit: { type: String, required: true }, -}); - -const AuditsSchema = mongoose.Schema({ - cls: { - type: AuditSchema, - required: true, - }, - si: { - type: AuditSchema, - required: true, - }, - fcp: { - type: AuditSchema, - required: true, - }, - lcp: { - type: AuditSchema, - required: true, - }, - tbt: { - type: AuditSchema, - required: true, - }, -}); - -/** - * Mongoose schema for storing metrics from Google Lighthouse. - * @typedef {Object} PageSpeedCheck - * @property {mongoose.Schema.Types.ObjectId} monitorId - Reference to the Monitor model. - * @property {number} accessibility - Accessibility score. - * @property {number} bestPractices - Best practices score. - * @property {number} seo - SEO score. - * @property {number} performance - Performance score. - */ - -const PageSpeedCheck = mongoose.Schema( - { - ...BaseCheckSchema.obj, - accessibility: { - type: Number, - required: true, - }, - bestPractices: { - type: Number, - required: true, - }, - seo: { - type: Number, - required: true, - }, - performance: { - type: Number, - required: true, - }, - audits: { - type: AuditsSchema, - required: true, - }, - }, - { timestamps: true } -); - -/** - * Mongoose model for storing metrics from Google Lighthouse. - * @typedef {mongoose.Model} LighthouseMetricsModel - */ - -PageSpeedCheck.pre("save", async function (next) { - try { - const monitor = await mongoose.model("Monitor").findById(this.monitorId); - if (monitor && monitor.status !== this.status) { - if (monitor.status === true && this.status === false) { - logger.info({ message: "Monitor went down", monitorId: monitor._id }); - } - - if (monitor.status === false && this.status === true) { - logger.info({ message: "Monitor went up", monitorId: monitor._id }); - } - monitor.status = this.status; - await monitor.save(); - } - } catch (error) { - logger.error({ - message: error.message, - service: "PageSpeedCheck", - method: "pre-save", - stack: error.stack, - }); - } finally { - next(); - } -}); - -PageSpeedCheck.index({ createdAt: 1 }); -PageSpeedCheck.index({ monitorId: 1, createdAt: 1 }); -PageSpeedCheck.index({ monitorId: 1, createdAt: -1 }); - -export default mongoose.model("PageSpeedCheck", PageSpeedCheck); diff --git a/Server/db/models/RecoveryToken.js b/Server/db/models/RecoveryToken.js deleted file mode 100644 index 2219a4bca..000000000 --- a/Server/db/models/RecoveryToken.js +++ /dev/null @@ -1,25 +0,0 @@ -import mongoose from "mongoose"; - -const RecoveryTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 600, - }, - }, - { - timestamps: true, - } -); - -export default mongoose.model("RecoveryToken", RecoveryTokenSchema); diff --git a/Server/db/models/StatusPage.js b/Server/db/models/StatusPage.js deleted file mode 100644 index bb1392d05..000000000 --- a/Server/db/models/StatusPage.js +++ /dev/null @@ -1,70 +0,0 @@ -import mongoose from "mongoose"; - -const StatusPageSchema = mongoose.Schema( - { - userId: { - type: mongoose.Schema.Types.ObjectId, - ref: "User", - immutable: true, - required: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - type: { - type: String, - required: true, - default: "uptime", - enum: ["uptime", "distributed"], - }, - companyName: { - type: String, - required: true, - default: "", - }, - url: { - type: String, - unique: true, - required: true, - default: "", - }, - timezone: { - type: String, - required: false, - }, - color: { - type: String, - required: false, - default: "#4169E1", - }, - monitors: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - required: true, - }, - ], - logo: { - data: Buffer, - contentType: String, - }, - isPublished: { - type: Boolean, - default: false, - }, - showCharts: { - type: Boolean, - default: true, - }, - showUptimePercentage: { - type: Boolean, - default: true, - }, - }, - { timestamps: true } -); - -export default mongoose.model("StatusPage", StatusPageSchema); diff --git a/Server/db/models/Team.js b/Server/db/models/Team.js deleted file mode 100644 index 95337c201..000000000 --- a/Server/db/models/Team.js +++ /dev/null @@ -1,14 +0,0 @@ -import mongoose from "mongoose"; -const TeamSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - }, - { - timestamps: true, - } -); -export default mongoose.model("Team", TeamSchema); diff --git a/Server/db/models/User.js b/Server/db/models/User.js deleted file mode 100644 index 3ddc5cee0..000000000 --- a/Server/db/models/User.js +++ /dev/null @@ -1,92 +0,0 @@ -import mongoose from "mongoose"; -import bcrypt from "bcrypt"; -import logger from "../../utils/logger.js"; - -const UserSchema = mongoose.Schema( - { - firstName: { - type: String, - required: true, - }, - lastName: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - avatarImage: { - type: String, - }, - profileImage: { - data: Buffer, - contentType: String, - }, - isActive: { - type: Boolean, - default: true, - }, - isVerified: { - type: Boolean, - default: false, - }, - role: { - type: [String], - default: "user", - enum: ["user", "admin", "superadmin", "demo"], - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - }, - checkTTL: { - type: Number, - }, - }, - { - timestamps: true, - } -); - -UserSchema.pre("save", async function (next) { - if (!this.isModified("password")) { - next(); - } - const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait - this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync - next(); -}); - -UserSchema.pre("findOneAndUpdate", async function (next) { - const update = this.getUpdate(); - if ("password" in update) { - const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait - update.password = await bcrypt.hash(update.password, salt); // hash is also async, need to eitehr await or use hashSync - } - - next(); -}); - -UserSchema.methods.comparePassword = async function (submittedPassword) { - const res = await bcrypt.compare(submittedPassword, this.password); - return res; -}; - -const User = mongoose.model("User", UserSchema); - -User.init().then(() => { - logger.info({ - message: "User model initialized", - service: "UserModel", - method: "init", - }); -}); - -export default mongoose.model("User", UserSchema); diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js deleted file mode 100644 index 8f035c2dc..000000000 --- a/Server/db/mongo/MongoDB.js +++ /dev/null @@ -1,147 +0,0 @@ -import mongoose from "mongoose"; -import UserModel from "../models/User.js"; -import AppSettings from "../models/AppSettings.js"; -import logger from "../../utils/logger.js"; - -//**************************************** -// User Operations -//**************************************** - -import * as userModule from "./modules/userModule.js"; - -//**************************************** -// Invite Token Operations -//**************************************** - -import * as inviteModule from "./modules/inviteModule.js"; - -//**************************************** -// Recovery Operations -//**************************************** -import * as recoveryModule from "./modules/recoveryModule.js"; - -//**************************************** -// Monitors -//**************************************** - -import * as monitorModule from "./modules/monitorModule.js"; - -//**************************************** -// Page Speed Checks -//**************************************** - -import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; - -//**************************************** -// Hardware Checks -//**************************************** -import * as hardwareCheckModule from "./modules/hardwareCheckModule.js"; - -//**************************************** -// Checks -//**************************************** - -import * as checkModule from "./modules/checkModule.js"; - -//**************************************** -// Distributed Checks -//**************************************** -import * as distributedCheckModule from "./modules/distributedCheckModule.js"; - -//**************************************** -// Maintenance Window -//**************************************** -import * as maintenanceWindowModule from "./modules/maintenanceWindowModule.js"; - -//**************************************** -// Notifications -//**************************************** -import * as notificationModule from "./modules/notificationModule.js"; - -//**************************************** -// AppSettings -//**************************************** -import * as settingsModule from "./modules/settingsModule.js"; - -//**************************************** -// Status Page -//**************************************** -import * as statusPageModule from "./modules/statusPageModule.js"; - -class MongoDB { - static SERVICE_NAME = "MongoDB"; - - constructor() { - Object.assign(this, userModule); - Object.assign(this, inviteModule); - Object.assign(this, recoveryModule); - Object.assign(this, monitorModule); - Object.assign(this, pageSpeedCheckModule); - Object.assign(this, hardwareCheckModule); - Object.assign(this, checkModule); - Object.assign(this, distributedCheckModule); - Object.assign(this, maintenanceWindowModule); - Object.assign(this, notificationModule); - Object.assign(this, settingsModule); - Object.assign(this, statusPageModule); - } - - connect = async () => { - try { - const connectionString = - process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db"; - await mongoose.connect(connectionString); - // If there are no AppSettings, create one - let appSettings = await AppSettings.find(); - if (appSettings.length === 0) { - appSettings = new AppSettings({}); - await appSettings.save(); - } - // Sync indexes - const models = mongoose.modelNames(); - for (const modelName of models) { - const model = mongoose.model(modelName); - await model.syncIndexes(); - } - - logger.info({ - message: "Connected to MongoDB", - service: this.SERVICE_NAME, - method: "connect", - }); - } catch (error) { - logger.error({ - message: error.message, - service: this.SERVICE_NAME, - method: "connect", - stack: error.stack, - }); - throw error; - } - }; - - disconnect = async () => { - try { - logger.info({ message: "Disconnecting from MongoDB" }); - await mongoose.disconnect(); - logger.info({ message: "Disconnected from MongoDB" }); - return; - } catch (error) { - logger.error({ - message: error.message, - service: this.SERVICE_NAME, - method: "disconnect", - stack: error.stack, - }); - } - }; - checkSuperadmin = async (req, res) => { - const superAdmin = await UserModel.findOne({ role: "superadmin" }); - if (superAdmin !== null) { - return true; - } - return false; - }; -} - -export default MongoDB; diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js deleted file mode 100644 index 6450b78a2..000000000 --- a/Server/db/mongo/modules/checkModule.js +++ /dev/null @@ -1,301 +0,0 @@ -import Check from "../../models/Check.js"; -import Monitor from "../../models/Monitor.js"; -import User from "../../models/User.js"; -import logger from "../../../utils/logger.js"; -import { ObjectId } from "mongodb"; - -const SERVICE_NAME = "checkModule"; -const dateRangeLookup = { - hour: new Date(new Date().setHours(new Date().getHours() - 1)), - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: undefined, -}; - -/** - * Create a check for a monitor - * @async - * @param {Object} checkData - * @param {string} checkData.monitorId - * @param {boolean} checkData.status - * @param {number} checkData.responseTime - * @param {number} checkData.statusCode - * @param {string} checkData.message - * @returns {Promise} - * @throws {Error} - */ - -const createCheck = async (checkData) => { - try { - const { monitorId, status } = checkData; - const n = (await Check.countDocuments({ monitorId })) + 1; - const check = await new Check({ ...checkData }).save(); - const monitor = await Monitor.findById(monitorId); - - if (!monitor) { - logger.error({ - message: "Monitor not found", - service: SERVICE_NAME, - method: "createCheck", - details: `monitor ID: ${monitorId}`, - }); - return; - } - - // Update uptime percentage - if (monitor.uptimePercentage === undefined) { - monitor.uptimePercentage = status === true ? 1 : 0; - } else { - monitor.uptimePercentage = - (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; - } - - await monitor.save(); - return check; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; - } -}; - -/** - * Get all checks for a monitor - * @async - * @param {string} monitorId - * @returns {Promise>} - * @throws {Error} - */ -const getChecksByMonitor = async (req) => { - try { - const { monitorId } = req.params; - let { sortOrder, dateRange, filter, page, rowsPerPage, status } = req.query; - status = typeof status !== "undefined" ? false : undefined; - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - // Match - const matchStage = { - monitorId: ObjectId.createFromHexString(monitorId), - ...(typeof status !== "undefined" && { status }), - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], - }, - }), - }; - - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecks", - }); - break; - } - } - - //Sort - sortOrder = sortOrder === "asc" ? 1 : -1; - - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } - const checks = await Check.aggregate([ - { $match: matchStage }, - { $sort: { createdAt: sortOrder } }, - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { - $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], - }, - checks: { - $ifNull: ["$checks", []], - }, - }, - }, - ]); - - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecks"; - throw error; - } -}; - -const getChecksByTeam = async (req) => { - try { - let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query; - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - const { teamId } = req.params; - const matchStage = { - teamId: ObjectId.createFromHexString(teamId), - status: false, - ...(dateRangeLookup[dateRange] && { - createdAt: { - $gte: dateRangeLookup[dateRange], - }, - }), - }; - // Add filter to match stage - if (filter !== undefined) { - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - matchStage.statusCode = 5000; - break; - default: - logger.warn({ - message: "invalid filter", - service: SERVICE_NAME, - method: "getChecksByTeam", - }); - break; - } - } - - sortOrder = sortOrder === "asc" ? 1 : -1; - - // pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } - - const checks = await Check.aggregate([ - { $match: matchStage }, - { $sort: { createdAt: sortOrder } }, - - { - $facet: { - summary: [{ $count: "checksCount" }], - checks: [{ $skip: skip }, { $limit: rowsPerPage }], - }, - }, - { - $project: { - checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, - checks: "$checks", - }, - }, - ]); - - return checks[0]; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecksByTeam"; - throw error; - } -}; - -/** - * Delete all checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecks = async (monitorId) => { - try { - const result = await Check.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecks"; - throw error; - } -}; - -/** - * Delete all checks for a team - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deleteChecksByTeamId = async (teamId) => { - try { - const teamMonitors = await Monitor.find({ teamId: teamId }); - let totalDeletedCount = 0; - - await Promise.all( - teamMonitors.map(async (monitor) => { - const result = await Check.deleteMany({ monitorId: monitor._id }); - totalDeletedCount += result.deletedCount; - monitor.status = true; - await monitor.save(); - }) - ); - - return totalDeletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecksByTeamId"; - throw error; - } -}; - -const updateChecksTTL = async (teamId, ttl) => { - try { - await Check.collection.dropIndex("expiry_1"); - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "updateChecksTTL", - stack: error.stack, - }); - } - - try { - await Check.collection.createIndex( - { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary - ); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - // Update user - try { - await User.updateMany({ teamId: teamId }, { checkTTL: ttl }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } -}; - -export { - createCheck, - getChecksByMonitor, - getChecksByTeam, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -}; diff --git a/Server/db/mongo/modules/distributedCheckModule.js b/Server/db/mongo/modules/distributedCheckModule.js deleted file mode 100644 index 71238a847..000000000 --- a/Server/db/mongo/modules/distributedCheckModule.js +++ /dev/null @@ -1,26 +0,0 @@ -import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js"; -const SERVICE_NAME = "distributedCheckModule"; - -const createDistributedCheck = async (checkData) => { - try { - const check = await new DistributedUptimeCheck({ ...checkData }).save(); - return check; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; - } -}; - -const deleteDistributedChecksByMonitorId = async (monitorId) => { - try { - const result = await DistributedUptimeCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteDistributedChecksByMonitorId"; - throw error; - } -}; - -export { createDistributedCheck, deleteDistributedChecksByMonitorId }; diff --git a/Server/db/mongo/modules/hardwareCheckModule.js b/Server/db/mongo/modules/hardwareCheckModule.js deleted file mode 100644 index 25f14bae1..000000000 --- a/Server/db/mongo/modules/hardwareCheckModule.js +++ /dev/null @@ -1,58 +0,0 @@ -import HardwareCheck from "../../models/HardwareCheck.js"; -import Monitor from "../../models/Monitor.js"; -import logger from "../../../utils/logger.js"; - -const SERVICE_NAME = "hardwareCheckModule"; -const createHardwareCheck = async (hardwareCheckData) => { - try { - const { monitorId, status } = hardwareCheckData; - const n = (await HardwareCheck.countDocuments({ monitorId })) + 1; - const monitor = await Monitor.findById(monitorId); - - if (!monitor) { - logger.error({ - message: "Monitor not found", - service: SERVICE_NAME, - method: "createHardwareCheck", - details: `monitor ID: ${monitorId}`, - }); - return null; - } - - let newUptimePercentage; - if (monitor.uptimePercentage === undefined) { - newUptimePercentage = status === true ? 1 : 0; - } else { - newUptimePercentage = - (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; - } - - await Monitor.findOneAndUpdate( - { _id: monitorId }, - { uptimePercentage: newUptimePercentage } - ); - - const hardwareCheck = await new HardwareCheck({ - ...hardwareCheckData, - }).save(); - return hardwareCheck; - } catch (error) { - console.log("error creating hardware check", error); - error.service = SERVICE_NAME; - error.method = "createHardwareCheck"; - throw error; - } -}; - -const deleteHardwareChecksByMonitorId = async (monitorId) => { - try { - const result = await HardwareCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteHardwareChecksByMonitorId"; - throw error; - } -}; - -export { createHardwareCheck, deleteHardwareChecksByMonitorId }; diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js deleted file mode 100644 index f5c960697..000000000 --- a/Server/db/mongo/modules/inviteModule.js +++ /dev/null @@ -1,89 +0,0 @@ -import InviteToken from "../../models/InviteToken.js"; -import crypto from "crypto"; -import ServiceRegistry from "../../../service/serviceRegistry.js"; -import StringService from "../../../service/stringService.js"; - -const SERVICE_NAME = "inviteModule"; -/** - * Request an invite token for a user. - * - * This function deletes any existing invite tokens for the user's email, - * generates a new token, saves it, and then returns the new token. - * - * @param {Object} userData - The user data. - * @param {string} userData.email - The user's email. - * @param {mongoose.Schema.Types.ObjectId} userData.teamId - The ID of the team. - * @param {Array} userData.role - The user's role(s). - * @param {Date} [userData.expiry=Date.now] - The expiry date of the token. Defaults to the current date and time. - * @returns {Promise} The invite token. - * @throws {Error} If there is an error. - */ -const requestInviteToken = async (userData) => { - try { - await InviteToken.deleteMany({ email: userData.email }); - userData.token = crypto.randomBytes(32).toString("hex"); - let inviteToken = new InviteToken(userData); - await inviteToken.save(); - return inviteToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestInviteToken"; - throw error; - } -}; - -/** - * Retrieves an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteToken = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOne({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); - } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteToken"; - throw error; - } -}; - -/** - * Retrieves and deletes an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteTokenAndDelete = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOneAndDelete({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); - } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteTokenAndDelete"; - throw error; - } -}; - -export { requestInviteToken, getInviteToken, getInviteTokenAndDelete }; diff --git a/Server/db/mongo/modules/maintenanceWindowModule.js b/Server/db/mongo/modules/maintenanceWindowModule.js deleted file mode 100644 index 072f83c3d..000000000 --- a/Server/db/mongo/modules/maintenanceWindowModule.js +++ /dev/null @@ -1,228 +0,0 @@ -import MaintenanceWindow from "../../models/MaintenanceWindow.js"; -const SERVICE_NAME = "maintenanceWindowModule"; - -/** - * Asynchronously creates a new MaintenanceWindow document and saves it to the database. - * If the maintenance window is a one-time event, the expiry field is set to the same value as the end field. - * @async - * @function createMaintenanceWindow - * @param {Object} maintenanceWindowData - The data for the new MaintenanceWindow document. - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowData.monitorId - The ID of the monitor. - * @param {Boolean} maintenanceWindowData.active - Indicates whether the maintenance window is active. - * @param {Boolean} maintenanceWindowData.oneTime - Indicates whether the maintenance window is a one-time event. - * @param {Date} maintenanceWindowData.start - The start date and time of the maintenance window. - * @param {Date} maintenanceWindowData.end - The end date and time of the maintenance window. - * @returns {Promise} The saved MaintenanceWindow document. - * @throws {Error} If there is an error saving the document. - * @example - * const maintenanceWindowData = { - * monitorId: 'yourMonitorId', - * active: true, - * oneTime: true, - * start: new Date(), - * end: new Date(), - * }; - * createMaintenanceWindow(maintenanceWindowData) - * .then(maintenanceWindow => console.log(maintenanceWindow)) - * .catch(error => console.error(error)); - */ -const createMaintenanceWindow = async (maintenanceWindowData) => { - try { - const maintenanceWindow = new MaintenanceWindow({ - ...maintenanceWindowData, - }); - - // If the maintenance window is a one time window, set the expiry to the end date - if (maintenanceWindowData.oneTime) { - maintenanceWindow.expiry = maintenanceWindowData.end; - } - const result = await maintenanceWindow.save(); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMaintenanceWindow"; - throw error; - } -}; - -const getMaintenanceWindowById = async (maintenanceWindowId) => { - try { - const maintenanceWindow = await MaintenanceWindow.findById({ - _id: maintenanceWindowId, - }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific team ID. - * @async - * @function getMaintenanceWindowByUserId - * @param {String} teamId - The ID of the team. - * @param {Object} query - The request body. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - * @example - * getMaintenanceWindowByUserId(teamId) - * .then(maintenanceWindows => console.log(maintenanceWindows)) - * .catch(error => console.error(error)); - */ -const getMaintenanceWindowsByTeamId = async (teamId, query) => { - try { - let { active, page, rowsPerPage, field, order } = query || {}; - const maintenanceQuery = { teamId }; - - if (active !== undefined) maintenanceQuery.active = active; - - const maintenanceWindowCount = - await MaintenanceWindow.countDocuments(maintenanceQuery); - - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } - - // Sorting - let sort = {}; - if (field !== undefined && order !== undefined) { - sort[field] = order === "asc" ? 1 : -1; - } - - const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery) - .skip(skip) - .limit(rowsPerPage) - .sort(sort); - - return { maintenanceWindows, maintenanceWindowCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowByUserId"; - throw error; - } -}; - -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function getMaintenanceWindowsByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - * @example - * getMaintenanceWindowsByMonitorId('monitorId') - * .then(maintenanceWindows => console.log(maintenanceWindows)) - * .catch(error => console.error(error)); - */ -const getMaintenanceWindowsByMonitorId = async (monitorId) => { - try { - const maintenanceWindows = await MaintenanceWindow.find({ - monitorId: monitorId, - }); - return maintenanceWindows; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowsByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes a MaintenanceWindow document by its ID. - * @async - * @function deleteMaintenanceWindowById - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowId - The ID of the MaintenanceWindow document to delete. - * @returns {Promise} The deleted MaintenanceWindow document. - * @throws {Error} If there is an error deleting the document. - * @example - * deleteMaintenanceWindowById('maintenanceWindowId') - * .then(maintenanceWindow => console.log(maintenanceWindow)) - * .catch(error => console.error(error)); - */ -const deleteMaintenanceWindowById = async (maintenanceWindowId) => { - try { - const maintenanceWindow = - await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function deleteMaintenanceWindowByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - * deleteMaintenanceWindowByMonitorId('monitorId') - * .then(result => console.log(result)) - * .catch(error => console.error(error)); - */ -const deleteMaintenanceWindowByMonitorId = async (monitorId) => { - try { - const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific user ID. - * @async - * @function deleteMaintenanceWindowByUserId - * @param {String} userId - The ID of the user. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - * deleteMaintenanceWindowByUserId('userId') - * .then(result => console.log(result)) - * .catch(error => console.error(error)); - */ -const deleteMaintenanceWindowByUserId = async (userId) => { - try { - const result = await MaintenanceWindow.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByUserId"; - throw error; - } -}; - -const editMaintenanceWindowById = async (maintenanceWindowId, maintenanceWindowData) => { - try { - const editedMaintenanceWindow = await MaintenanceWindow.findByIdAndUpdate( - maintenanceWindowId, - maintenanceWindowData, - { new: true } - ); - return editedMaintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMaintenanceWindowById"; - throw error; - } -}; - -export { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -}; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js deleted file mode 100644 index 14d883c23..000000000 --- a/Server/db/mongo/modules/monitorModule.js +++ /dev/null @@ -1,937 +0,0 @@ -import Monitor from "../../models/Monitor.js"; -import Check from "../../models/Check.js"; -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -import HardwareCheck from "../../models/HardwareCheck.js"; -import DistributedUptimeCheck from "../../models/DistributedUptimeCheck.js"; -import Notification from "../../models/Notification.js"; -import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/serviceRegistry.js"; -import StringService from "../../../service/stringService.js"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { - buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, - buildDistributedUptimeDetailsPipeline, -} from "./monitorModuleQueries.js"; -import { ObjectId } from "mongodb"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); -const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); - -const SERVICE_NAME = "monitorModule"; - -const CHECK_MODEL_LOOKUP = { - http: Check, - ping: Check, - docker: Check, - port: Check, - pagespeed: PageSpeedCheck, - hardware: HardwareCheck, -}; - -/** - * Get all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getAllMonitors = async (req, res) => { - try { - const monitors = await Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; - } -}; - -/** - * Get all monitors with uptime stats for 1,7,30, and 90 days - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getAllMonitorsWithUptimeStats = async () => { - const timeRanges = { - 1: new Date(Date.now() - 24 * 60 * 60 * 1000), - 7: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), - 30: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), - 90: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000), - }; - - try { - const monitors = await Monitor.find(); - const monitorsWithStats = await Promise.all( - monitors.map(async (monitor) => { - const model = CHECK_MODEL_LOOKUP[monitor.type]; - - const uptimeStats = await Promise.all( - Object.entries(timeRanges).map(async ([days, startDate]) => { - const checks = await model.find({ - monitorId: monitor._id, - createdAt: { $gte: startDate }, - }); - return [days, getUptimePercentage(checks)]; - }) - ); - - return { - ...monitor.toObject(), - ...Object.fromEntries(uptimeStats), - }; - }) - ); - - return monitorsWithStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitorsWithUptimeStats"; - throw error; - } -}; - -/** - * Function to calculate uptime duration based on the most recent check. - * @param {Array} checks Array of check objects. - * @returns {number} Uptime duration in ms. - */ -const calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; - - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; - } - } - - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } - - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; -}; - -/** - * Helper function to get duration since last check - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); -}; - -/** - * Helper function to get latestResponseTime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - return checks[0]?.responseTime ?? 0; -}; - -/** - * Helper function to get average response time - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const validChecks = checks.filter((check) => typeof check.responseTime === "number"); - if (validChecks.length === 0) { - return 0; - } - const aggResponseTime = validChecks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / validChecks.length; -}; - -/** - * Helper function to get percentage 24h uptime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; -}; - -/** - * Helper function to get all incidents - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); -}; - -/** - * Get date range parameters - * @param {string} dateRange - 'day' | 'week' | 'month' | 'all' - * @returns {Object} Start and end dates - */ -const getDateRange = (dateRange) => { - const startDates = { - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: new Date(0), - }; - return { - start: startDates[dateRange], - end: new Date(), - }; -}; - -/** - * Get checks for a monitor - * @param {string} monitorId - Monitor ID - * @param {Object} model - Check model to use - * @param {Object} dateRange - Date range parameters - * @param {number} sortOrder - Sort order (1 for ascending, -1 for descending) - * @returns {Promise} All checks and date-ranged checks - */ -const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { - const indexSpec = { - monitorId: 1, - createdAt: sortOrder, // This will be 1 or -1 - }; - - const [checksAll, checksForDateRange] = await Promise.all([ - model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - model - .find({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }) - .hint(indexSpec) - .lean(), - ]); - - return { checksAll, checksForDateRange }; -}; - -/** - * Process checks for display - * @param {Array} checks - Checks to process - * @param {number} numToDisplay - Number of checks to display - * @param {boolean} normalize - Whether to normalize the data - * @returns {Array} Processed checks - */ -const processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { - let processedChecks = checks; - if (numToDisplay && checks.length > numToDisplay) { - const n = Math.ceil(checks.length / numToDisplay); - processedChecks = checks.filter((_, index) => index % n === 0); - } - return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; -}; - -/** - * Get time-grouped checks based on date range - * @param {Array} checks Array of check objects - * @param {string} dateRange 'day' | 'week' | 'month' - * @returns {Object} Grouped checks by time period - */ -const groupChecksByTime = (checks, dateRange) => { - return checks.reduce((acc, check) => { - // Validate the date - const checkDate = new Date(check.createdAt); - if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { - return acc; - } - - const time = - dateRange === "day" - ? checkDate.setMinutes(0, 0, 0) - : checkDate.toISOString().split("T")[0]; - - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); -}; - -/** - * Calculate aggregate stats for a group of checks - * @param {Object} group Group of checks - * @returns {Object} Stats for the group - */ -const calculateGroupStats = (group) => { - const totalChecks = group.checks.length; - - const checksWithResponseTime = group.checks.filter( - (check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime) - ); - - return { - time: group.time, - uptimePercentage: getUptimePercentage(group.checks), - totalChecks, - totalIncidents: group.checks.filter((check) => !check.status).length, - avgResponseTime: - checksWithResponseTime.length > 0 - ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / - checksWithResponseTime.length - : 0, - }; -}; - -/** - * Get uptime details by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUptimeDetailsById = async (req) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const { monitorId } = req.params; - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(stringService.dbFindMonitorById(monitorId)); - } - - const { dateRange, normalize } = req.query; - const dates = getDateRange(dateRange); - const formatLookup = { - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - - const dateString = formatLookup[dateRange]; - - const results = await Check.aggregate( - buildUptimeDetailsPipeline(monitor, dates, dateString) - ); - - const monitorData = results[0]; - const normalizedGroupChecks = NormalizeDataUptimeDetails( - monitorData.groupedChecks, - 10, - 100 - ); - - const monitorStats = { - ...monitor.toObject(), - ...monitorData, - groupedChecks: normalizedGroupChecks, - }; - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUptimeDetailsById"; - throw error; - } -}; - -const getDistributedUptimeDetailsById = async (req) => { - try { - const { monitorId } = req?.params ?? {}; - if (typeof monitorId === "undefined") { - throw new Error(); - } - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(this.stringService.dbFindMonitorById(monitorId)); - } - - const { dateRange, normalize } = req.query; - const dates = getDateRange(dateRange); - const formatLookup = { - day: "%Y-%m-%dT%H:%M:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - - const dateString = formatLookup[dateRange]; - const results = await DistributedUptimeCheck.aggregate( - buildDistributedUptimeDetailsPipeline(monitor, dates, dateString) - ); - - const monitorData = results[0]; - const normalizedGroupChecks = NormalizeDataUptimeDetails( - monitorData.groupedChecks, - 10, - 100 - ); - - const monitorStats = { - ...monitor.toObject(), - ...monitorData, - groupedChecks: normalizedGroupChecks, - }; - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getDistributedUptimeDetailsById"; - throw error; - } -}; - -/** - * Get stats by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorStatsById = async (req) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const { monitorId } = req.params; - - // Get monitor, if we can't find it, abort with error - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const model = CHECK_MODEL_LOOKUP[monitor.type]; - const dates = getDateRange(dateRange); - const { checksAll, checksForDateRange } = await getMonitorChecks( - monitorId, - model, - dates, - sort - ); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: calculateUptimeDuration(checksAll), - lastChecked: getLastChecked(checksAll), - latestResponseTime: getLatestResponseTime(checksAll), - periodIncidents: getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: processChecksForDisplay( - NormalizeData, - checksForDateRange, - numToDisplay, - normalize - ), - }; - - if ( - monitor.type === "http" || - monitor.type === "ping" || - monitor.type === "docker" || - monitor.type === "port" - ) { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = getUptimePercentage(checksForDateRange); - const groupedChecks = groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } -}; - -const getHardwareDetailsById = async (req) => { - try { - const { monitorId } = req.params; - const { dateRange } = req.query; - const monitor = await Monitor.findById(monitorId); - const dates = getDateRange(dateRange); - const formatLookup = { - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - const dateString = formatLookup[dateRange]; - const hardwareStats = await HardwareCheck.aggregate( - buildHardwareDetailsPipeline(monitor, dates, dateString) - ); - - const monitorStats = { - ...monitor.toObject(), - stats: hardwareStats[0], - }; - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getHardwareDetailsById"; - throw error; - } -}; - -/** - * Get a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorById = async (monitorId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - const error = new Error(stringService.getDbFindMonitorById(monitorId)); - error.status = 404; - throw error; - } - // Get notifications - const notifications = await Notification.find({ - monitorId: monitorId, - }); - - // Update monitor with notifications and save - monitor.notifications = notifications; - await monitor.save(); - - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } -}; - -const getMonitorsByTeamId = async (req) => { - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build the match stage - const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; - const results = await Monitor.aggregate([ - { $match: matchStage }, - { - $facet: { - summary: [ - { - $group: { - _id: null, - totalMonitors: { $sum: 1 }, - upMonitors: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, - downMonitors: { - $sum: { - $cond: [{ $eq: ["$status", false] }, 1, 0], - }, - }, - pausedMonitors: { - $sum: { - $cond: [{ $eq: ["$isActive", false] }, 1, 0], - }, - }, - }, - }, - { - $project: { - _id: 0, - }, - }, - ], - monitors: [ - { $sort: sort }, - { - $project: { - _id: 1, - name: 1, - }, - }, - ], - filteredMonitors: [ - ...(filter !== undefined - ? [ - { - $match: { - $or: [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ], - }, - }, - ] - : []), - { $sort: sort }, - { $skip: skip }, - ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), - ...(limit - ? [ - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "standardchecks", - }, - }, - ] - : []), - ...(limit - ? [ - { - $lookup: { - from: "pagespeedchecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "pagespeedchecks", - }, - }, - ] - : []), - ...(limit - ? [ - { - $lookup: { - from: "hardwarechecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "hardwarechecks", - }, - }, - ] - : []), - ...(limit - ? [ - { - $lookup: { - from: "distributeduptimechecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "distributeduptimechecks", - }, - }, - ] - : []), - - { - $addFields: { - checks: { - $switch: { - branches: [ - { - case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, - then: "$standardchecks", - }, - { - case: { $eq: ["$type", "pagespeed"] }, - then: "$pagespeedchecks", - }, - { - case: { $eq: ["$type", "hardware"] }, - then: "$hardwarechecks", - }, - { - case: { $eq: ["$type", "distributed_http"] }, - then: "$distributeduptimechecks", - }, - ], - default: [], - }, - }, - }, - }, - { - $project: { - standardchecks: 0, - pagespeedchecks: 0, - hardwarechecks: 0, - }, - }, - ], - }, - }, - { - $project: { - summary: { $arrayElemAt: ["$summary", 0] }, - filteredMonitors: 1, - monitors: 1, - }, - }, - ]); - - let { monitors, filteredMonitors, summary } = results[0]; - filteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { - return monitor; - } - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - return { monitors, filteredMonitors, summary }; -}; - -/** - * Create a monitor - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const createMonitor = async (req, res) => { - try { - const monitor = new Monitor({ ...req.body }); - // Remove notifications fom monitor as they aren't needed here - monitor.notifications = undefined; - await monitor.save(); - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } -}; - -/** - * Delete a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteMonitor = async (req, res) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - const monitorId = req.params.monitorId; - try { - const monitor = await Monitor.findByIdAndDelete(monitorId); - if (!monitor) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); - } - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } -}; - -/** - * DELETE ALL MONITORS (TEMP) - */ - -const deleteAllMonitors = async (teamId) => { - try { - const monitors = await Monitor.find({ teamId }); - const { deletedCount } = await Monitor.deleteMany({ teamId }); - - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } -}; - -/** - * Delete all monitors associated with a user ID - * @async - * @param {string} userId - The ID of the user whose monitors are to be deleted. - * @returns {Promise} A promise that resolves when the operation is complete. - */ -const deleteMonitorsByUserId = async (userId) => { - try { - const result = await Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } -}; - -/** - * Edit a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const editMonitor = async (candidateId, candidateMonitor) => { - candidateMonitor.notifications = undefined; - - try { - const editedMonitor = await Monitor.findByIdAndUpdate(candidateId, candidateMonitor, { - new: true, - }); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } -}; - -const addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } -}; - -export { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorById, - getMonitorsByTeamId, - getUptimeDetailsById, - getDistributedUptimeDetailsById, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - getHardwareDetailsById, -}; - -// Helper functions -export { - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getDateRange, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -}; - -// limit 25 -// page 1 -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 25 -// sort { name: 1 } -// filteredMonitors [] - -// limit 25 -// page NaN -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 0 -// sort { name: 1 } diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js deleted file mode 100644 index 489017f51..000000000 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ /dev/null @@ -1,721 +0,0 @@ -const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { - return [ - { - $match: { - monitorId: monitor._id, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - lastCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - uptimeStreak: [ - { - $sort: { - createdAt: -1, - }, - }, - { - $group: { - _id: null, - checks: { $push: "$$ROOT" }, - }, - }, - { - $project: { - streak: { - $reduce: { - input: "$checks", - initialValue: { checks: [], foundFalse: false }, - in: { - $cond: [ - { - $and: [ - { $not: "$$value.foundFalse" }, // stop reducing if a false check has been found - { $eq: ["$$this.status", true] }, // continue reducing if current check true - ], - }, - // true case - { - checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, - foundFalse: false, // Add the check to the streak - }, - // false case - { - checks: "$$value.checks", - foundFalse: true, // Mark that we found a false - }, - ], - }, - }, - }, - }, - }, - ], - // For the response time chart, should return checks for date window - // Grouped by: {day: hour}, {week: day}, {month: day} - groupedChecks: [ - { - $match: { - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - { - $sort: { - _id: 1, - }, - }, - ], - // Average response time for the date window - groupAvgResponseTime: [ - { - $match: { - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - ], - // All UpChecks for the date window - upChecks: [ - { - $match: { - status: true, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - // Up checks grouped by: {day: hour}, {week: day}, {month: day} - groupedUpChecks: [ - { - $match: { - status: true, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - totalChecks: { - $sum: 1, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - // All down checks for the date window - downChecks: [ - { - $match: { - status: false, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window - groupedDownChecks: [ - { - $match: { - status: false, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - totalChecks: { - $sum: 1, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - }, - }, - { - $project: { - uptimeStreak: { - $let: { - vars: { - checks: { $ifNull: [{ $first: "$uptimeStreak.streak.checks" }, []] }, - }, - in: { - $cond: [ - { $eq: [{ $size: "$$checks" }, 0] }, - 0, - { - $subtract: [new Date(), { $last: "$$checks.createdAt" }], - }, - ], - }, - }, - }, - avgResponseTime: { - $arrayElemAt: ["$aggregateData.avgResponseTime", 0], - }, - totalChecks: { - $arrayElemAt: ["$aggregateData.totalChecks", 0], - }, - latestResponseTime: { - $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0], - }, - timeSinceLastCheck: { - $let: { - vars: { - lastCheck: { - $arrayElemAt: ["$aggregateData.lastCheck", 0], - }, - }, - in: { - $cond: [ - { - $ifNull: ["$$lastCheck", false], - }, - { - $subtract: [new Date(), "$$lastCheck.createdAt"], - }, - 0, - ], - }, - }, - }, - groupedChecks: "$groupedChecks", - groupedAvgResponseTime: { - $arrayElemAt: ["$groupAvgResponseTime", 0], - }, - upChecks: { - $arrayElemAt: ["$upChecks", 0], - }, - groupedUpChecks: "$groupedUpChecks", - downChecks: { - $arrayElemAt: ["$downChecks", 0], - }, - groupedDownChecks: "$groupedDownChecks", - }, - }, - ]; -}; - -const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { - return [ - { - $match: { - monitorId: monitor._id, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - latestCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - upChecks: [ - { - $match: { - status: true, - }, - }, - { - $group: { - _id: null, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - checks: [ - { - $limit: 1, - }, - { - $project: { - diskCount: { - $size: "$disk", - }, - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { - diskCount: "$diskCount", - }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { $eq: ["$monitorId", monitor._id] }, - { $gte: ["$createdAt", dates.start] }, - { $lte: ["$createdAt", dates.end] }, - ], - }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgCpuUsage: { - $avg: "$cpu.usage_percent", - }, - avgMemoryUsage: { - $avg: "$memory.usage_percent", - }, - avgTemperatures: { - $push: { - $ifNull: ["$cpu.temperature", [0]], - }, - }, - disks: { - $push: "$disk", - }, - }, - }, - { - $project: { - _id: 1, - avgCpuUsage: 1, - avgMemoryUsage: 1, - avgTemperature: { - $map: { - input: { - $range: [ - 0, - { - $size: { - // Handle null temperatures array - $ifNull: [ - { $arrayElemAt: ["$avgTemperatures", 0] }, - [0], // Default to single-element array if null - ], - }, - }, - ], - }, - as: "index", - in: { - $avg: { - $map: { - input: "$avgTemperatures", - as: "tempArray", - in: { - $ifNull: [ - { $arrayElemAt: ["$$tempArray", "$$index"] }, - 0, // Default to 0 if element is null - ], - }, - }, - }, - }, - }, - }, - disks: { - $map: { - input: { - $range: [0, "$$diskCount"], - }, - as: "diskIndex", - in: { - name: { - $concat: [ - "disk", - { - $toString: "$$diskIndex", - }, - ], - }, - readSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.read_speed_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - writeSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.write_speed_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - totalBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.total_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - freeBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: ["$$diskArray.free_bytes", "$$diskIndex"], - }, - }, - }, - }, - usagePercent: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.usage_percent", - "$$diskIndex", - ], - }, - }, - }, - }, - }, - }, - }, - }, - }, - ], - as: "hourlyStats", - }, - }, - { - $unwind: "$hourlyStats", - }, - { - $replaceRoot: { - newRoot: "$hourlyStats", - }, - }, - ], - }, - }, - { - $project: { - aggregateData: { - $arrayElemAt: ["$aggregateData", 0], - }, - upChecks: { - $arrayElemAt: ["$upChecks", 0], - }, - checks: { - $sortArray: { - input: "$checks", - sortBy: { _id: 1 }, - }, - }, - }, - }, - ]; -}; - -const buildDistributedUptimeDetailsPipeline = (monitor, dates, dateString) => { - return [ - { - $match: { - monitorId: monitor._id, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - lastCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - uptBurnt: { - $sum: "$uptBurnt", - }, - }, - }, - ], - // For the response time chart, should return checks for date window - // Grouped by: {day: hour}, {week: day}, {month: day} - groupedMapChecks: [ - { - $match: { - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: { - date: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - city: "$city", - lat: "$location.lat", - lng: "$location.lng", - }, - city: { $first: "$city" }, // Add this line to include city in output - lat: { $first: "$location.lat" }, - lng: { $first: "$location.lng" }, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - { - $sort: { - "_id.date": 1, - }, - }, - ], - groupedChecks: [ - { - $match: { - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgResponseTime: { - $avg: "$responseTime", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - { - $sort: { - _id: 1, - }, - }, - ], - // Average response time for the date window - groupAvgResponseTime: [ - { - $match: { - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $group: { - _id: null, - avgResponseTime: { - $avg: "$responseTime", - }, - }, - }, - ], - latestChecks: [ - { - $sort: { createdAt: -1 }, // Sort by newest first - }, - { - $limit: 5, // Get only the first 5 documents - }, - { - $project: { - responseTime: 1, - city: 1, - countryCode: 1, - uptBurnt: { $toString: "$uptBurnt" }, - }, - }, - ], - }, - }, - { - $project: { - totalUptBurnt: { - $toString: { - $arrayElemAt: ["$aggregateData.uptBurnt", 0], - }, - }, - avgResponseTime: { - $arrayElemAt: ["$aggregateData.avgResponseTime", 0], - }, - totalChecks: { - $arrayElemAt: ["$aggregateData.totalChecks", 0], - }, - latestResponseTime: { - $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0], - }, - timeSinceLastCheck: { - $let: { - vars: { - lastCheck: { - $arrayElemAt: ["$aggregateData.lastCheck", 0], - }, - }, - in: { - $cond: [ - { - $ifNull: ["$$lastCheck", false], - }, - { - $subtract: [new Date(), "$$lastCheck.createdAt"], - }, - 0, - ], - }, - }, - }, - groupedMapChecks: "$groupedMapChecks", - groupedChecks: "$groupedChecks", - groupedAvgResponseTime: { - $arrayElemAt: ["$groupAvgResponseTime", 0], - }, - latestChecks: "$latestChecks", - }, - }, - ]; -}; - -export { - buildUptimeDetailsPipeline, - buildHardwareDetailsPipeline, - buildDistributedUptimeDetailsPipeline, -}; diff --git a/Server/db/mongo/modules/notificationModule.js b/Server/db/mongo/modules/notificationModule.js deleted file mode 100644 index aab2d03bd..000000000 --- a/Server/db/mongo/modules/notificationModule.js +++ /dev/null @@ -1,56 +0,0 @@ -import Notification from "../../models/Notification.js"; -const SERVICE_NAME = "notificationModule"; -/** - * Creates a new notification. - * @param {Object} notificationData - The data for the new notification. - * @param {mongoose.Types.ObjectId} notificationData.monitorId - The ID of the monitor. - * @param {string} notificationData.type - The type of the notification (e.g., "email", "sms"). - * @param {string} [notificationData.address] - The address for the notification (if applicable). - * @param {string} [notificationData.phone] - The phone number for the notification (if applicable). - * @returns {Promise} The created notification. - * @throws Will throw an error if the notification cannot be created. - */ -const createNotification = async (notificationData) => { - try { - const notification = await new Notification({ ...notificationData }).save(); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNotification"; - throw error; - } -}; - -/** - * Retrieves notifications by monitor ID. - * @param {mongoose.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of notifications. - * @throws Will throw an error if the notifications cannot be retrieved. - */ -const getNotificationsByMonitorId = async (monitorId) => { - try { - const notifications = await Notification.find({ monitorId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByMonitorId"; - throw error; - } -}; - -const deleteNotificationsByMonitorId = async (monitorId) => { - try { - const result = await Notification.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationsByMonitorId"; - throw error; - } -}; - -export { - createNotification, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, -}; diff --git a/Server/db/mongo/modules/pageSpeedCheckModule.js b/Server/db/mongo/modules/pageSpeedCheckModule.js deleted file mode 100644 index 427893a96..000000000 --- a/Server/db/mongo/modules/pageSpeedCheckModule.js +++ /dev/null @@ -1,47 +0,0 @@ -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -const SERVICE_NAME = "pageSpeedCheckModule"; -/** - * Create a PageSpeed check for a monitor - * @async - * @param {Object} pageSpeedCheckData - * @param {string} pageSpeedCheckData.monitorId - * @param {number} pageSpeedCheckData.accessibility - * @param {number} pageSpeedCheckData.bestPractices - * @param {number} pageSpeedCheckData.seo - * @param {number} pageSpeedCheckData.performance - * @returns {Promise} - * @throws {Error} - */ -const createPageSpeedCheck = async (pageSpeedCheckData) => { - try { - const pageSpeedCheck = await new PageSpeedCheck({ - ...pageSpeedCheckData, - }).save(); - return pageSpeedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } -}; - -/** - * Delete all PageSpeed checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deletePageSpeedChecksByMonitorId = async (monitorId) => { - try { - const result = await PageSpeedCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deletePageSpeedChecksByMonitorId"; - throw error; - } -}; - -export { createPageSpeedCheck, deletePageSpeedChecksByMonitorId }; diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js deleted file mode 100644 index 3b39e847c..000000000 --- a/Server/db/mongo/modules/recoveryModule.js +++ /dev/null @@ -1,88 +0,0 @@ -import UserModel from "../../models/User.js"; -import RecoveryToken from "../../models/RecoveryToken.js"; -import crypto from "crypto"; -import serviceRegistry from "../../../service/serviceRegistry.js"; -import StringService from "../../../service/stringService.js"; - -const SERVICE_NAME = "recoveryModule"; - -/** - * Request a recovery token - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const requestRecoveryToken = async (req, res) => { - try { - // Delete any existing tokens - await RecoveryToken.deleteMany({ email: req.body.email }); - let recoveryToken = new RecoveryToken({ - email: req.body.email, - token: crypto.randomBytes(32).toString("hex"), - }); - await recoveryToken.save(); - return recoveryToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestRecoveryToken"; - throw error; - } -}; - -const validateRecoveryToken = async (req, res) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const candidateToken = req.body.recoveryToken; - const recoveryToken = await RecoveryToken.findOne({ - token: candidateToken, - }); - if (recoveryToken !== null) { - return recoveryToken; - } else { - throw new Error(stringService.dbTokenNotFound); - } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "validateRecoveryToken"; - throw error; - } -}; - -const resetPassword = async (req, res) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const newPassword = req.body.password; - - // Validate token again - const recoveryToken = await validateRecoveryToken(req, res); - const user = await UserModel.findOne({ email: recoveryToken.email }); - - if (user === null) { - throw new Error(stringService.dbUserNotFound); - } - - const match = await user.comparePassword(newPassword); - if (match === true) { - throw new Error(stringService.dbResetPasswordBadMatch); - } - - user.password = newPassword; - await user.save(); - await RecoveryToken.deleteMany({ email: recoveryToken.email }); - // Fetch the user again without the password - const userWithoutPassword = await UserModel.findOne({ - email: recoveryToken.email, - }) - .select("-password") - .select("-profileImage"); - return userWithoutPassword; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "resetPassword"; - throw error; - } -}; - -export { requestRecoveryToken, validateRecoveryToken, resetPassword }; diff --git a/Server/db/mongo/modules/settingsModule.js b/Server/db/mongo/modules/settingsModule.js deleted file mode 100644 index 3b5f68e19..000000000 --- a/Server/db/mongo/modules/settingsModule.js +++ /dev/null @@ -1,30 +0,0 @@ -import AppSettings from "../../models/AppSettings.js"; -const SERVICE_NAME = "SettingsModule"; - -const getAppSettings = async () => { - try { - const settings = AppSettings.findOne(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getSettings"; - throw error; - } -}; - -const updateAppSettings = async (newSettings) => { - try { - const settings = await AppSettings.findOneAndUpdate( - {}, - { $set: newSettings }, - { new: true } - ); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateAppSettings"; - throw error; - } -}; - -export { getAppSettings, updateAppSettings }; diff --git a/Server/db/mongo/modules/statusPageModule.js b/Server/db/mongo/modules/statusPageModule.js deleted file mode 100644 index f970bbb3f..000000000 --- a/Server/db/mongo/modules/statusPageModule.js +++ /dev/null @@ -1,211 +0,0 @@ -import StatusPage from "../../models/StatusPage.js"; -import { NormalizeData } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/serviceRegistry.js"; -import StringService from "../../../service/stringService.js"; - -const SERVICE_NAME = "statusPageModule"; - -const createStatusPage = async (statusPageData, image) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPage = new StatusPage({ ...statusPageData }); - if (image) { - statusPage.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } - await statusPage.save(); - return statusPage; - } catch (error) { - if (error?.code === 11000) { - // Handle duplicate URL errors - error.status = 400; - error.message = stringService.statusPageUrlNotUnique; - } - error.service = SERVICE_NAME; - error.method = "createStatusPage"; - throw error; - } -}; - -const updateStatusPage = async (statusPageData, image) => { - try { - if (image) { - statusPageData.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } - const statusPage = await StatusPage.findOneAndUpdate( - { url: statusPageData.url }, - statusPageData, - { - new: true, - } - ); - - return statusPage; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateStatusPage"; - throw error; - } -}; - -const getStatusPageByUrl = async (url, type) => { - try { - if (type === "distributed") { - const statusPage = await StatusPage.aggregate([{ $match: { url } }]); - return statusPage[0]; - } else { - return getStatusPage(url); - } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const getStatusPagesByTeamId = async (teamId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPages = await StatusPage.find({ teamId }); - return statusPages; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPagesByTeamId"; - throw error; - } -}; - -const getStatusPage = async (url) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPageQuery = await StatusPage.aggregate([ - { $match: { url: url } }, - { - $set: { - originalMonitors: "$monitors", - }, - }, - { - $lookup: { - from: "monitors", - localField: "monitors", - foreignField: "_id", - as: "monitors", - }, - }, - { - $unwind: "$monitors", - }, - { - $lookup: { - from: "checks", - let: { monitorId: "$monitors._id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - { $limit: 25 }, - ], - as: "monitors.checks", - }, - }, - { - $addFields: { - "monitors.orderIndex": { - $indexOfArray: ["$originalMonitors", "$monitors._id"], - }, - }, - }, - { - $group: { - _id: "$_id", - statusPage: { $first: "$$ROOT" }, - monitors: { $push: "$monitors" }, - }, - }, - { - $project: { - statusPage: { - _id: 1, - color: 1, - companyName: 1, - isPublished: 1, - logo: 1, - originalMonitors: 1, - showCharts: 1, - showUptimePercentage: 1, - timezone: 1, - url: 1, - }, - monitors: { - $sortArray: { - input: "$monitors", - sortBy: { orderIndex: 1 }, - }, - }, - }, - }, - ]); - if (!statusPageQuery.length) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; - throw error; - } - - const { statusPage, monitors } = statusPageQuery[0]; - - const normalizedMonitors = monitors.map((monitor) => { - return { - ...monitor, - checks: NormalizeData(monitor.checks, 10, 100), - }; - }); - - return { statusPage, monitors: normalizedMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const deleteStatusPage = async (url) => { - try { - await StatusPage.deleteOne({ url }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPage"; - throw error; - } -}; - -const deleteStatusPagesByMonitorId = async (monitorId) => { - try { - await StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPageByMonitorId"; - throw error; - } -}; - -export { - createStatusPage, - updateStatusPage, - getStatusPagesByTeamId, - getStatusPage, - getStatusPageByUrl, - deleteStatusPage, - deleteStatusPagesByMonitorId, -}; diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js deleted file mode 100644 index 4069be8c6..000000000 --- a/Server/db/mongo/modules/userModule.js +++ /dev/null @@ -1,232 +0,0 @@ -import UserModel from "../../models/User.js"; -import TeamModel from "../../models/Team.js"; -import { GenerateAvatarImage } from "../../../utils/imageProcessing.js"; - -const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -import { ParseBoolean } from "../../../utils/utils.js"; -import ServiceRegistry from "../../../service/serviceRegistry.js"; -import StringService from "../../../service/stringService.js"; -const SERVICE_NAME = "userModule"; - -/** - * Insert a User - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const insertUser = async ( - userData, - imageFile, - generateAvatarImage = GenerateAvatarImage -) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - if (imageFile) { - // 1. Save the full size image - userData.profileImage = { - data: imageFile.buffer, - contentType: imageFile.mimetype, - }; - - // 2. Get the avatar sized image - const avatar = await generateAvatarImage(imageFile); - userData.avatarImage = avatar; - } - - // Handle creating team if superadmin - if (userData.role.includes("superadmin")) { - const team = new TeamModel({ - email: userData.email, - }); - userData.teamId = team._id; - userData.checkTTL = 60 * 60 * 24 * 30; - await team.save(); - } - - const newUser = new UserModel(userData); - await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }) - .select("-password") - .select("-profileImage"); // .select() doesn't work with create, need to save then find - } catch (error) { - if (error.code === DUPLICATE_KEY_CODE) { - error.message = stringService.dbUserExists; - } - error.service = SERVICE_NAME; - error.method = "insertUser"; - throw error; - } -}; - -/** - * Get User by Email - * Gets a user by Email. Not sure if we'll ever need this except for login. - * If not needed except for login, we can move password comparison here - * Throws error if user not found - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUserByEmail = async (email) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - // Need the password to be able to compare, removed .select() - // We can strip the hash before returning the user - const user = await UserModel.findOne({ email: email }).select("-profileImage"); - if (!user) { - throw new Error(stringService.dbUserNotFound); - } - return user; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserByEmail"; - throw error; - } -}; - -/** - * Update a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ - -const updateUser = async ( - req, - res, - parseBoolean = ParseBoolean, - generateAvatarImage = GenerateAvatarImage -) => { - const candidateUserId = req.params.userId; - try { - const candidateUser = { ...req.body }; - // ****************************************** - // Handle profile image - // ****************************************** - - if (parseBoolean(candidateUser.deleteProfileImage) === true) { - candidateUser.profileImage = null; - candidateUser.avatarImage = null; - } else if (req.file) { - // 1. Save the full size image - candidateUser.profileImage = { - data: req.file.buffer, - contentType: req.file.mimetype, - }; - - // 2. Get the avatar sized image - const avatar = await generateAvatarImage(req.file); - candidateUser.avatarImage = avatar; - } - - // ****************************************** - // End handling profile image - // ****************************************** - - const updatedUser = await UserModel.findByIdAndUpdate( - candidateUserId, - candidateUser, - { new: true } // Returns updated user instead of pre-update user - ) - .select("-password") - .select("-profileImage"); - return updatedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateUser"; - throw error; - } -}; - -/** - * Delete a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteUser = async (userId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const deletedUser = await UserModel.findByIdAndDelete(userId); - if (!deletedUser) { - throw new Error(stringService.dbUserNotFound); - } - return deletedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteUser"; - throw error; - } -}; - -/** - * Delete a user by ID - * @async - * @param {string} teamId - * @returns {void} - * @throws {Error} - */ -const deleteTeam = async (teamId) => { - try { - await TeamModel.findByIdAndDelete(teamId); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteTeam"; - throw error; - } -}; - -const deleteAllOtherUsers = async () => { - try { - await UserModel.deleteMany({ role: { $ne: "superadmin" } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllOtherUsers"; - throw error; - } -}; - -const getAllUsers = async (req, res) => { - try { - const users = await UserModel.find().select("-password").select("-profileImage"); - return users; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllUsers"; - throw error; - } -}; - -const logoutUser = async (userId) => { - try { - await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "logoutUser"; - throw error; - } -}; - -export { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, -}; diff --git a/Server/db/mongo/utils/seedDb.js b/Server/db/mongo/utils/seedDb.js deleted file mode 100644 index cd4abb8d5..000000000 --- a/Server/db/mongo/utils/seedDb.js +++ /dev/null @@ -1,57 +0,0 @@ -import Monitor from "../../models/Monitor.js"; -import Check from "../../models/Check.js"; - -const generateRandomUrl = () => { - const domains = ["example.com", "test.org", "demo.net", "sample.io", "mock.dev"]; - const paths = ["api", "status", "health", "ping", "check"]; - return `https://${domains[Math.floor(Math.random() * domains.length)]}/${paths[Math.floor(Math.random() * paths.length)]}`; -}; - -const generateChecks = (monitorId, teamId, count) => { - const checks = []; - const endTime = new Date(Date.now() - 10 * 60 * 1000); // 10 minutes ago - const startTime = new Date(endTime - count * 60 * 1000); // count minutes before endTime - - for (let i = 0; i < count; i++) { - const timestamp = new Date(startTime.getTime() + i * 60 * 1000); - const status = Math.random() > 0.05; // 95% chance of being up - - checks.push({ - monitorId, - teamId, - status, - responseTime: Math.floor(Math.random() * 1000), // Random response time between 0-1000ms - createdAt: timestamp, - updatedAt: timestamp, - }); - } - - return checks; -}; - -const seedDb = async (userId, teamId) => { - try { - console.log("Deleting all monitors and checks"); - await Monitor.deleteMany({}); - await Check.deleteMany({}); - console.log("Adding monitors"); - for (let i = 0; i < 300; i++) { - const monitor = await Monitor.create({ - name: `Monitor ${i}`, - url: generateRandomUrl(), - type: "http", - userId, - teamId, - interval: 60000, - active: false, - }); - console.log(`Adding monitor and checks for monitor ${i}`); - const checks = generateChecks(monitor._id, teamId, 10000); - await Check.insertMany(checks); - } - } catch (error) { - console.error(error); - } -}; - -export default seedDb; diff --git a/Server/eslint.config.js b/Server/eslint.config.js deleted file mode 100644 index 13f1fc038..000000000 --- a/Server/eslint.config.js +++ /dev/null @@ -1,30 +0,0 @@ -import globals from "globals"; -import pluginJs from "@eslint/js"; -import mochaPlugin from "eslint-plugin-mocha"; - -/* -Please do not forget to look at the latest eslint configurations and rules. -ESlint v9 configuration is different than v8. -"https://eslint.org/docs/latest/use/configure/" -*/ - -/** @type {import('eslint').Linter.Config[]} */ -export default [ - { - languageOptions: { - globals: { - ...globals.node, // Add Node.js globals - ...globals.chai, // Add Chai globals - }, - ecmaVersion: 2023, - sourceType: "module", - }, - }, - pluginJs.configs.recommended, // Core JS rules - mochaPlugin.configs.flat.recommended, // Mocha rules - { - rules: { - "mocha/max-top-level-suites": "warn", // Warn if there are too many top-level suites instead of failing - }, - }, -]; diff --git a/Server/index.js b/Server/index.js deleted file mode 100644 index b28fd5a82..000000000 --- a/Server/index.js +++ /dev/null @@ -1,320 +0,0 @@ -import path from "path"; -import fs from "fs"; -import swaggerUi from "swagger-ui-express"; - -import express from "express"; -import helmet from "helmet"; -import cors from "cors"; -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 AuthRoutes from "./routes/authRoute.js"; -import AuthController from "./controllers/authController.js"; - -import InviteRoutes from "./routes/inviteRoute.js"; -import InviteController from "./controllers/inviteController.js"; - -import MonitorRoutes from "./routes/monitorRoute.js"; -import MonitorController from "./controllers/monitorController.js"; - -import CheckRoutes from "./routes/checkRoute.js"; -import CheckController from "./controllers/checkController.js"; - -import MaintenanceWindowRoutes from "./routes/maintenanceWindowRoute.js"; -import MaintenanceWindowController from "./controllers/maintenanceWindowController.js"; - -import SettingsRoutes from "./routes/settingsRoute.js"; -import SettingsController from "./controllers/settingsController.js"; - -import StatusPageRoutes from "./routes/statusPageRoute.js"; -import StatusPageController from "./controllers/statusPageController.js"; - -import QueueRoutes from "./routes/queueRoute.js"; -import QueueController from "./controllers/queueController.js"; - -import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js"; -import DistributedUptimeController from "./controllers/distributedUptimeController.js"; - -import NotificationRoutes from "./routes/notificationRoute.js"; - -import NotificationController from "./controllers/notificationController.js"; - -//JobQueue service and dependencies -import JobQueue from "./service/jobQueue.js"; -import { Queue, Worker } from "bullmq"; - -//Network service and dependencies -import NetworkService from "./service/networkService.js"; -import axios from "axios"; -import ping from "ping"; -import http from "http"; -import Docker from "dockerode"; -import net from "net"; -// Email service and dependencies -import EmailService from "./service/emailService.js"; -import nodemailer from "nodemailer"; -import pkg from "handlebars"; -const { compile } = pkg; -import mjml2html from "mjml"; - -// Settings Service and dependencies -import SettingsService from "./service/settingsService.js"; -import AppSettings from "./db/models/AppSettings.js"; - -// Status Service and dependencies -import StatusService from "./service/statusService.js"; - -// Notification Service and dependencies -import NotificationService from "./service/notificationService.js"; - -// Service Registry -import ServiceRegistry from "./service/serviceRegistry.js"; - -import MongoDB from "./db/mongo/MongoDB.js"; - -import IORedis from "ioredis"; - -import TranslationService from "./service/translationService.js"; -import languageMiddleware from "./middleware/languageMiddleware.js"; -import StringService from "./service/stringService.js"; - -const SERVICE_NAME = "Server"; -const SHUTDOWN_TIMEOUT = 1000; -let isShuttingDown = false; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const openApiSpec = JSON.parse( - fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8") -); - -let server; - -const PORT = 5000; - -const shutdown = async () => { - if (isShuttingDown) { - return; - } - isShuttingDown = true; - logger.info({ message: "Attempting graceful shutdown" }); - setTimeout(async () => { - logger.error({ - message: "Could not shut down in time, forcing shutdown", - service: SERVICE_NAME, - method: "shutdown", - }); - // flush Redis - const settings = - ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings() || {}; - - const { redisHost = "127.0.0.1", redisPort = 6379 } = settings; - const redis = new IORedis({ - host: redisHost, - port: redisPort, - }); - logger.info({ message: "Flushing Redis" }); - await redis.flushall(); - logger.info({ message: "Redis flushed" }); - process.exit(1); - }, SHUTDOWN_TIMEOUT); - try { - server.close(); - await ServiceRegistry.get(JobQueue.SERVICE_NAME).obliterate(); - await ServiceRegistry.get(MongoDB.SERVICE_NAME).disconnect(); - logger.info({ message: "Graceful shutdown complete" }); - process.exit(0); - } catch (error) { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "shutdown", - stack: error.stack, - }); - } -}; -// Need to wrap server setup in a function to handle async nature of JobQueue -const startApp = async () => { - const app = express(); - - // Create and Register Primary services - const translationService = new TranslationService(logger); - const stringService = new StringService(translationService); - ServiceRegistry.register(StringService.SERVICE_NAME, stringService); - - // Create DB - const db = new MongoDB(); - await db.connect(); - - // Create services - const networkService = new NetworkService( - axios, - ping, - logger, - http, - Docker, - net, - stringService - ); - const settingsService = new SettingsService(AppSettings); - await settingsService.loadSettings(); - const emailService = new EmailService( - settingsService, - fs, - path, - compile, - mjml2html, - nodemailer, - logger - ); - const statusService = new StatusService(db, logger); - const notificationService = new NotificationService(emailService, db, logger, networkService, stringService); - - const jobQueue = new JobQueue( - db, - statusService, - networkService, - notificationService, - settingsService, - stringService, - logger, - Queue, - Worker - ); - - // Register services - ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue); - ServiceRegistry.register(MongoDB.SERVICE_NAME, db); - ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService); - ServiceRegistry.register(EmailService.SERVICE_NAME, emailService); - ServiceRegistry.register(NetworkService.SERVICE_NAME, networkService); - ServiceRegistry.register(StatusService.SERVICE_NAME, statusService); - ServiceRegistry.register(NotificationService.SERVICE_NAME, notificationService); - ServiceRegistry.register(TranslationService.SERVICE_NAME, translationService); - - await translationService.initialize(); - - server = app.listen(PORT, () => { - logger.info({ message: `server started on port:${PORT}` }); - }); - - process.on("SIGUSR2", shutdown); - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); - - //Create controllers - const authController = new AuthController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(EmailService.SERVICE_NAME), - ServiceRegistry.get(JobQueue.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const monitorController = new MonitorController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(JobQueue.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const settingsController = new SettingsController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const checkController = new CheckController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const inviteController = new InviteController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(EmailService.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const maintenanceWindowController = new MaintenanceWindowController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(SettingsService.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const queueController = new QueueController( - ServiceRegistry.get(JobQueue.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const statusPageController = new StatusPageController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const notificationController = new NotificationController( - ServiceRegistry.get(NotificationService.SERVICE_NAME), - ServiceRegistry.get(StringService.SERVICE_NAME) - ); - - const distributedUptimeController = new DistributedUptimeController( - ServiceRegistry.get(MongoDB.SERVICE_NAME), - http, - ServiceRegistry.get(StatusService.SERVICE_NAME) - ); - - //Create routes - const authRoutes = new AuthRoutes(authController); - const monitorRoutes = new MonitorRoutes(monitorController); - const settingsRoutes = new SettingsRoutes(settingsController); - const checkRoutes = new CheckRoutes(checkController); - const inviteRoutes = new InviteRoutes(inviteController); - const maintenanceWindowRoutes = new MaintenanceWindowRoutes( - maintenanceWindowController - ); - const queueRoutes = new QueueRoutes(queueController); - const statusPageRoutes = new StatusPageRoutes(statusPageController); - const distributedUptimeRoutes = new DistributedUptimeRoutes( - distributedUptimeController - ); - - const notificationRoutes = new NotificationRoutes(notificationController); - - // Init job queue - await jobQueue.initJobQueue(); - // Middleware - app.use(cors()); - app.use(express.json()); - app.use(helmet()); - app.use(languageMiddleware(stringService, translationService)); - // Swagger UI - app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec)); - - //routes - app.use(responseHandler); - - app.use("/api/v1/auth", authRoutes.getRouter()); - app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter()); - app.use("/api/v1/invite", inviteRoutes.getRouter()); - app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter()); - app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter()); - app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter()); - app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); - app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter()); - app.use("/api/v1/status-page", statusPageRoutes.getRouter()); - app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); - app.use(handleErrors); -}; - -startApp().catch((error) => { - logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "startApp", - stack: error.stack, - }); - process.exit(1); -}); diff --git a/Server/locales/en.json b/Server/locales/en.json deleted file mode 100644 index 8657deb73..000000000 --- a/Server/locales/en.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "dontHaveAccount": "Don't have account", - "email": "E-mail", - "forgotPassword": "Forgot Password", - "password": "password", - "signUp": "Sign up", - "submit": "Submit", - "title": "Title", - "continue": "Continue", - "enterEmail": "Enter your email", - "authLoginTitle": "Log In", - "authLoginEnterPassword": "Enter your password", - "commonPassword": "Password", - "commonBack": "Back", - "authForgotPasswordTitle": "Forgot password?", - "authForgotPasswordResetPassword": "Reset password", - "createPassword": "Create your password", - "createAPassword": "Create a password", - "authRegisterAlreadyHaveAccount": "Already have an account?", - "commonAppName": "BlueWave Uptime", - "authLoginEnterEmail": "Enter your email", - "authRegisterTitle": "Create an account", - "authRegisterStepOneTitle": "Create your account", - "authRegisterStepOneDescription": "Enter your details to get started", - "authRegisterStepTwoTitle": "Set up your profile", - "authRegisterStepTwoDescription": "Tell us more about yourself", - "authRegisterStepThreeTitle": "Almost done!", - "authRegisterStepThreeDescription": "Review your information", - "authForgotPasswordDescription": "No worries, we'll send you reset instructions.", - "authForgotPasswordSendInstructions": "Send instructions", - "authForgotPasswordBackTo": "Back to", - "authCheckEmailTitle": "Check your email", - "authCheckEmailDescription": "We sent a password reset link to {{email}}", - "authCheckEmailResendEmail": "Resend email", - "authCheckEmailBackTo": "Back to", - "goBackTo": "Go back to", - "authCheckEmailDidntReceiveEmail": "Didn't receive the email?", - "authCheckEmailClickToResend": "Click to resend", - "authSetNewPasswordTitle": "Set new password", - "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.", - "authSetNewPasswordNewPassword": "New password", - "authSetNewPasswordConfirmPassword": "Confirm password", - "confirmPassword": "Confirm your password", - "authSetNewPasswordResetPassword": "Reset password", - "authSetNewPasswordBackTo": "Back to", - "authPasswordMustBeAtLeast": "Must be at least", - "authPasswordCharactersLong": "8 characters long", - "authPasswordMustContainAtLeast": "Must contain at least", - "authPasswordSpecialCharacter": "one special character", - "authPasswordOneNumber": "one number", - "authPasswordUpperCharacter": "one upper character", - "authPasswordLowerCharacter": "one lower character", - "authPasswordConfirmAndPassword": "Confirm password and password", - "authPasswordMustMatch": "must match", - "friendlyError": "Something went wrong...", - "unknownError": "An unknown error occurred", - "unauthorized": "Unauthorized access", - "authAdminExists": "Admin already exists", - "authInviteNotFound": "Invite not found", - "unknownService": "Unknown service", - "noAuthToken": "No auth token provided", - "invalidAuthToken": "Invalid auth token", - "expiredAuthToken": "Token expired", - "noRefreshToken": "No refresh token provided", - "invalidRefreshToken": "Invalid refresh token", - "expiredRefreshToken": "Refresh token expired", - "requestNewAccessToken": "Request new access token", - "invalidPayload": "Invalid payload", - "verifyOwnerNotFound": "Document not found", - "verifyOwnerUnauthorized": "Unauthorized access", - "insufficientPermissions": "Insufficient permissions", - "dbUserExists": "User already exists", - "dbUserNotFound": "User not found", - "dbTokenNotFound": "Token not found", - "dbResetPasswordBadMatch": "New password must be different from old password", - "dbFindMonitorById": "Monitor with id ${monitorId} not found", - "dbDeleteChecks": "No checks found for monitor with id ${monitorId}", - "authIncorrectPassword": "Incorrect password", - "authUnauthorized": "Unauthorized access", - "monitorGetById": "Monitor not found", - "monitorGetByUserId": "No monitors found for user", - "jobQueueWorkerClose": "Error closing worker", - "jobQueueDeleteJob": "Job not found in queue", - "jobQueueObliterate": "Error obliterating queue", - "pingCannotResolve": "No response", - "statusPageNotFound": "Status page not found", - "statusPageUrlNotUnique": "Status page url must be unique", - "dockerFail": "Failed to fetch Docker container information", - "dockerNotFound": "Docker container not found", - "portFail": "Failed to connect to port", - "alertCreate": "Alert created successfully", - "alertGetByUser": "Got alerts successfully", - "alertGetByMonitor": "Got alerts by Monitor successfully", - "alertGetById": "Got alert by Id successfully", - "alertEdit": "Alert edited successfully", - "alertDelete": "Alert deleted successfully", - "authCreateUser": "User created successfully", - "authLoginUser": "User logged in successfully", - "authLogoutUser": "User logged out successfully", - "authUpdateUser": "User updated successfully", - "authCreateRecoveryToken": "Recovery token created successfully", - "authVerifyRecoveryToken": "Recovery token verified successfully", - "authResetPassword": "Password reset successfully", - "authAdminCheck": "Admin check completed successfully", - "authDeleteUser": "User deleted successfully", - "authTokenRefreshed": "Auth token is refreshed", - "authGetAllUsers": "Got all users successfully", - "inviteIssued": "Invite sent successfully", - "inviteVerified": "Invite verified successfully", - "checkCreate": "Check created successfully", - "checkGet": "Got checks successfully", - "checkDelete": "Checks deleted successfully", - "checkUpdateTtl": "Checks TTL updated successfully", - "monitorGetAll": "Got all monitors successfully", - "monitorStatsById": "Got monitor stats by Id successfully", - "monitorGetByIdSuccess": "Got monitor by Id successfully", - "monitorGetByTeamId": "Got monitors by Team Id successfully", - "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully", - "monitorCreate": "Monitor created successfully", - "monitorDelete": "Monitor deleted successfully", - "monitorEdit": "Monitor edited successfully", - "monitorCertificate": "Got monitor certificate successfully", - "monitorDemoAdded": "Successfully added demo monitors", - "queueGetMetrics": "Got metrics successfully", - "queueAddJob": "Job added successfully", - "queueObliterate": "Queue obliterated", - "jobQueueDeleteJobSuccess": "Job removed successfully", - "jobQueuePauseJob": "Job paused successfully", - "jobQueueResumeJob": "Job resumed successfully", - "maintenanceWindowGetById": "Got Maintenance Window by Id successfully", - "maintenanceWindowCreate": "Maintenance Window created successfully", - "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully", - "maintenanceWindowDelete": "Maintenance Window deleted successfully", - "maintenanceWindowEdit": "Maintenance Window edited successfully", - "pingSuccess": "Success", - "getAppSettings": "Got app settings successfully", - "updateAppSettings": "Updated app settings successfully", - "statusPageByUrl": "Got status page by url successfully", - "statusPageCreate": "Status page created successfully", - "newTermsAdded": "New terms added to POEditor", - "dockerSuccess": "Docker container status fetched successfully", - "portSuccess": "Port connected successfully", - "monitorPause": "Monitor paused successfully", - "monitorResume": "Monitor resumed successfully", - "statusPageDelete": "Status page deleted successfully", - "statusPageUpdate": "Status page updated successfully", - "statusPageByTeamId": "Got status pages by team id successfully", - "httpNetworkError": "Network error", - "httpNotJson": "Response data is not json", - "httpJsonPathError": "Failed to parse json data", - "httpEmptyResult": "Result is empty", - "httpMatchSuccess": "Response data match successfully", - "httpMatchFail": "Failed to match response data" -} \ No newline at end of file diff --git a/Server/locales/en.json.bak b/Server/locales/en.json.bak deleted file mode 100644 index eaef58eef..000000000 --- a/Server/locales/en.json.bak +++ /dev/null @@ -1,147 +0,0 @@ -{ - "dontHaveAccount": "Don't have account", - "email": "E-mail", - "forgotPassword": "Forgot Password", - "password": "password", - "signUp": "Sign up", - "submit": "Submit", - "title": "Title", - "continue": "Continue", - "enterEmail": "Enter your email", - "authLoginTitle": "Log In", - "authLoginEnterPassword": "Enter your password", - "commonPassword": "Password", - "commonBack": "Back", - "authForgotPasswordTitle": "Forgot password?", - "authForgotPasswordResetPassword": "Reset password", - "createPassword": "Create your password", - "createAPassword": "Create a password", - "authRegisterAlreadyHaveAccount": "Already have an account?", - "commonAppName": "BlueWave Uptime", - "authLoginEnterEmail": "Enter your email", - "authRegisterTitle": "Create an account", - "authRegisterStepOneTitle": "Create your account", - "authRegisterStepOneDescription": "Enter your details to get started", - "authRegisterStepTwoTitle": "Set up your profile", - "authRegisterStepTwoDescription": "Tell us more about yourself", - "authRegisterStepThreeTitle": "Almost done!", - "authRegisterStepThreeDescription": "Review your information", - "authForgotPasswordDescription": "No worries, we'll send you reset instructions.", - "authForgotPasswordSendInstructions": "Send instructions", - "authForgotPasswordBackTo": "Back to", - "authCheckEmailTitle": "Check your email", - "authCheckEmailDescription": "We sent a password reset link to {{email}}", - "authCheckEmailResendEmail": "Resend email", - "authCheckEmailBackTo": "Back to", - "goBackTo": "Go back to", - "authCheckEmailDidntReceiveEmail": "Didn't receive the email?", - "authCheckEmailClickToResend": "Click to resend", - "authSetNewPasswordTitle": "Set new password", - "authSetNewPasswordDescription": "Your new password must be different from previously used passwords.", - "authSetNewPasswordNewPassword": "New password", - "authSetNewPasswordConfirmPassword": "Confirm password", - "confirmPassword": "Confirm your password", - "authSetNewPasswordResetPassword": "Reset password", - "authSetNewPasswordBackTo": "Back to", - "authPasswordMustBeAtLeast": "Must be at least", - "authPasswordCharactersLong": "8 characters long", - "authPasswordMustContainAtLeast": "Must contain at least", - "authPasswordSpecialCharacter": "one special character", - "authPasswordOneNumber": "one number", - "authPasswordUpperCharacter": "one upper character", - "authPasswordLowerCharacter": "one lower character", - "authPasswordConfirmAndPassword": "Confirm password and password", - "authPasswordMustMatch": "must match", - "friendlyError": "Something went wrong...", - "unknownError": "An unknown error occurred", - "unauthorized": "Unauthorized access", - "authAdminExists": "Admin already exists", - "authInviteNotFound": "Invite not found", - "unknownService": "Unknown service", - "noAuthToken": "No auth token provided", - "invalidAuthToken": "Invalid auth token", - "expiredAuthToken": "Token expired", - "noRefreshToken": "No refresh token provided", - "invalidRefreshToken": "Invalid refresh token", - "expiredRefreshToken": "Refresh token expired", - "requestNewAccessToken": "Request new access token", - "invalidPayload": "Invalid payload", - "verifyOwnerNotFound": "Document not found", - "verifyOwnerUnauthorized": "Unauthorized access", - "insufficientPermissions": "Insufficient permissions", - "dbUserExists": "User already exists", - "dbUserNotFound": "User not found", - "dbTokenNotFound": "Token not found", - "dbResetPasswordBadMatch": "New password must be different from old password", - "dbFindMonitorById": "Monitor with id ${monitorId} not found", - "dbDeleteChecks": "No checks found for monitor with id ${monitorId}", - "authIncorrectPassword": "Incorrect password", - "authUnauthorized": "Unauthorized access", - "monitorGetById": "Monitor not found", - "monitorGetByUserId": "No monitors found for user", - "jobQueueWorkerClose": "Error closing worker", - "jobQueueDeleteJob": "Job not found in queue", - "jobQueueObliterate": "Error obliterating queue", - "pingCannotResolve": "No response", - "statusPageNotFound": "Status page not found", - "statusPageUrlNotUnique": "Status page url must be unique", - "dockerFail": "Failed to fetch Docker container information", - "dockerNotFound": "Docker container not found", - "portFail": "Failed to connect to port", - "alertCreate": "Alert created successfully", - "alertGetByUser": "Got alerts successfully", - "alertGetByMonitor": "Got alerts by Monitor successfully", - "alertGetById": "Got alert by Id successfully", - "alertEdit": "Alert edited successfully", - "alertDelete": "Alert deleted successfully", - "authCreateUser": "User created successfully", - "authLoginUser": "User logged in successfully", - "authLogoutUser": "User logged out successfully", - "authUpdateUser": "User updated successfully", - "authCreateRecoveryToken": "Recovery token created successfully", - "authVerifyRecoveryToken": "Recovery token verified successfully", - "authResetPassword": "Password reset successfully", - "authAdminCheck": "Admin check completed successfully", - "authDeleteUser": "User deleted successfully", - "authTokenRefreshed": "Auth token is refreshed", - "authGetAllUsers": "Got all users successfully", - "inviteIssued": "Invite sent successfully", - "inviteVerified": "Invite verified successfully", - "checkCreate": "Check created successfully", - "checkGet": "Got checks successfully", - "checkDelete": "Checks deleted successfully", - "checkUpdateTtl": "Checks TTL updated successfully", - "monitorGetAll": "Got all monitors successfully", - "monitorStatsById": "Got monitor stats by Id successfully", - "monitorGetByIdSuccess": "Got monitor by Id successfully", - "monitorGetByTeamId": "Got monitors by Team Id successfully", - "monitorGetByUserIdSuccess": "Got monitor for ${userId} successfully", - "monitorCreate": "Monitor created successfully", - "monitorDelete": "Monitor deleted successfully", - "monitorEdit": "Monitor edited successfully", - "monitorCertificate": "Got monitor certificate successfully", - "monitorDemoAdded": "Successfully added demo monitors", - "queueGetMetrics": "Got metrics successfully", - "queueAddJob": "Job added successfully", - "queueObliterate": "Queue obliterated", - "jobQueueDeleteJobSuccess": "Job removed successfully", - "jobQueuePauseJob": "Job paused successfully", - "jobQueueResumeJob": "Job resumed successfully", - "maintenanceWindowGetById": "Got Maintenance Window by Id successfully", - "maintenanceWindowCreate": "Maintenance Window created successfully", - "maintenanceWindowGetByTeam": "Got Maintenance Windows by Team successfully", - "maintenanceWindowDelete": "Maintenance Window deleted successfully", - "maintenanceWindowEdit": "Maintenance Window edited successfully", - "pingSuccess": "Success", - "getAppSettings": "Got app settings successfully", - "updateAppSettings": "Updated app settings successfully", - "statusPageByUrl": "Got status page by url successfully", - "statusPageCreate": "Status page created successfully", - "newTermsAdded": "New terms added to POEditor", - "dockerSuccess": "Docker container status fetched successfully", - "portSuccess": "Port connected successfully", - "monitorPause": "Monitor paused successfully", - "monitorResume": "Monitor resumed successfully", - "statusPageDelete": "Status page deleted successfully", - "statusPageUpdate": "Status page updated successfully" -} diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js deleted file mode 100644 index b64cda897..000000000 --- a/Server/middleware/handleErrors.js +++ /dev/null @@ -1,22 +0,0 @@ -import logger from "../utils/logger.js"; -import ServiceRegistry from "../service/serviceRegistry.js"; -import StringService from "../service/stringService.js"; - -const handleErrors = (error, req, res, next) => { - const status = error.status || 500; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const message = error.message || stringService.friendlyError; - const service = error.service || stringService.unknownService; - logger.error({ - message: message, - service: service, - method: error.method, - stack: error.stack, - }); - res.error({ - status, - msg: message, - }); -}; - -export { handleErrors }; diff --git a/Server/middleware/isAllowed.js b/Server/middleware/isAllowed.js deleted file mode 100644 index 03a6b9d16..000000000 --- a/Server/middleware/isAllowed.js +++ /dev/null @@ -1,60 +0,0 @@ -import jwt from "jsonwebtoken"; -const TOKEN_PREFIX = "Bearer "; -const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../service/serviceRegistry.js"; -import StringService from "../service/stringService.js"; -import SettingsService from "../service/settingsService.js"; - - -const isAllowed = (allowedRoles) => { - return (req, res, next) => { - const token = req.headers["authorization"]; - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - // If no token is pressent, return an error - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - - // If the token is improperly formatted, return an error - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); - error.status = 400; - error.service = SERVICE_NAME; - next(error); - return; - } - // Parse the token - try { - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - const { jwtSecret } = ServiceRegistry.get( - SettingsService.SERVICE_NAME - ).getSettings(); - var decoded = jwt.verify(parsedToken, jwtSecret); - const userRoles = decoded.role; - - // Check if the user has the required role - if (userRoles.some((role) => allowedRoles.includes(role))) { - next(); - return; - } else { - const error = new Error(stringService.insufficientPermissions); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - } catch (error) { - error.status = 401; - error.method = "isAllowed"; - error.service = SERVICE_NAME; - next(error); - return; - } - }; -}; - -export { isAllowed }; diff --git a/Server/middleware/languageMiddleware.js b/Server/middleware/languageMiddleware.js deleted file mode 100644 index 11c2f2aec..000000000 --- a/Server/middleware/languageMiddleware.js +++ /dev/null @@ -1,11 +0,0 @@ -const languageMiddleware = (stringService, translationService) => (req, res, next) => { - const acceptLanguage = req.headers['accept-language'] || 'en'; - const language = acceptLanguage.split(',')[0].slice(0, 2).toLowerCase(); - - translationService.setLanguage(language); - stringService.setLanguage(language); - - next(); -}; - -export default languageMiddleware; \ No newline at end of file diff --git a/Server/middleware/responseHandler.js b/Server/middleware/responseHandler.js deleted file mode 100644 index 061422f50..000000000 --- a/Server/middleware/responseHandler.js +++ /dev/null @@ -1,20 +0,0 @@ -const responseHandler = (req, res, next) => { - res.success = ({ status = 200, msg = "OK", data = null }) => { - return res.status(status).json({ - success: true, - msg: msg, - data: data, - }); - }; - - res.error = ({ status = 500, msg = "Internal server error", data = null }) => { - return res.status(status).json({ - success: false, - msg, - data, - }); - }; - next(); -}; - -export { responseHandler }; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js deleted file mode 100644 index 9ee42e809..000000000 --- a/Server/middleware/verifyJWT.js +++ /dev/null @@ -1,97 +0,0 @@ -import jwt from "jsonwebtoken"; -import ServiceRegistry from "../service/serviceRegistry.js"; -import SettingsService from "../service/settingsService.js"; -import StringService from "../service/stringService.js"; -const SERVICE_NAME = "verifyJWT"; -const TOKEN_PREFIX = "Bearer "; - -/** - * Verifies the JWT token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @returns {express.Response} - */ -const verifyJWT = (req, res, next) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifyJWT"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // Verify the token's authenticity - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - if (err.name === "TokenExpiredError") { - // token has expired - handleExpiredJwtToken(req, res, next); - } else { - // Invalid token (signature or token altered or other issue) - const errorMessage = stringService.invalidAuthToken; - return res.status(401).json({ success: false, msg: errorMessage }); - } - } else { - // Token is valid and not expired, carry on with request, Add the decoded payload to the request - req.user = decoded; - next(); - } - }); -}; - -function handleExpiredJwtToken(req, res, next) { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - // check for refreshToken - const refreshToken = req.headers["x-refresh-token"]; - - if (!refreshToken) { - // No refresh token provided - const error = new Error(stringService.noRefreshToken); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "handleExpiredJwtToken"; - return next(error); - } - - // Verify refresh token - const { refreshTokenSecret } = ServiceRegistry.get( - SettingsService.SERVICE_NAME - ).getSettings(); - jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => { - if (refreshErr) { - // Invalid or expired refresh token, trigger logout - const errorMessage = - refreshErr.name === "TokenExpiredError" - ? stringService.expiredRefreshToken - : stringService.invalidRefreshToken; - const error = new Error(errorMessage); - error.status = 401; - error.service = SERVICE_NAME; - return next(error); - } - - // Refresh token is valid and unexpired, request for new access token - res.status(403).json({ - success: false, - msg: stringService.requestNewAccessToken, - }); - }); -} - -export { verifyJWT }; diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js deleted file mode 100644 index ca2476f54..000000000 --- a/Server/middleware/verifyOwnership.js +++ /dev/null @@ -1,53 +0,0 @@ -import logger from "../utils/logger.js"; -import ServiceRegistry from "../service/serviceRegistry.js"; -import StringService from "../service/stringService.js"; -const SERVICE_NAME = "verifyOwnership"; - -const verifyOwnership = (Model, paramName) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - return async (req, res, next) => { - const userId = req.user._id; - const documentId = req.params[paramName]; - try { - const doc = await Model.findById(documentId); - //If the document is not found, return a 404 error - if (!doc) { - logger.error({ - message: stringService.verifyOwnerNotFound, - service: SERVICE_NAME, - method: "verifyOwnership", - }); - const error = new Error(stringService.verifyOwnerNotFound); - error.status = 404; - throw error; - } - - // Special case for User model, as it will not have a `userId` field as other docs will - if (Model.modelName === "User") { - if (userId.toString() !== doc._id.toString()) { - const error = new Error(stringService.verifyOwnerUnauthorized); - error.status = 403; - throw error; - } - next(); - return; - } - - // If the userID does not match the document's userID, return a 403 error - if (userId.toString() !== doc.userId.toString()) { - const error = new Error(stringService.verifyOwnerUnauthorized); - error.status = 403; - throw error; - } - next(); - return; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyOwnership"; - next(error); - return; - } - }; -}; - -export { verifyOwnership }; diff --git a/Server/middleware/verifySuperAdmin.js b/Server/middleware/verifySuperAdmin.js deleted file mode 100644 index 98c336fad..000000000 --- a/Server/middleware/verifySuperAdmin.js +++ /dev/null @@ -1,68 +0,0 @@ -const jwt = require("jsonwebtoken"); -const logger = require("../utils/logger"); -const SERVICE_NAME = "verifyAdmin"; -const TOKEN_PREFIX = "Bearer "; -import ServiceRegistry from "../service/serviceRegistry.js"; -import SettingsService from "../service/settingsService.js"; -import StringService from "../service/stringService.js"; -/** - * Verifies the JWT token - * @function - * @param {express.Request} req - * @param {express.Response} res - * @param {express.NextFunction} next - * @returns {express.Response} - */ -const verifySuperAdmin = (req, res, next) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(stringService.noAuthToken); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifySuperAdmin"; - next(error); - return; - } - - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // verify admin role is present - const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings(); - - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - logger.error({ - message: err.message, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - details: stringService.invalidAuthToken, - }); - return res - .status(401) - .json({ success: false, msg: stringService.invalidAuthToken }); - } - - if (decoded.role.includes("superadmin") === false) { - logger.error({ - message: stringService.invalidAuthToken, - service: SERVICE_NAME, - method: "verifySuperAdmin", - stack: err.stack, - }); - return res.status(401).json({ success: false, msg: stringService.unauthorized }); - } - next(); - }); -}; - -module.exports = { verifySuperAdmin }; diff --git a/Server/nodemon.json b/Server/nodemon.json deleted file mode 100644 index b43c80fc1..000000000 --- a/Server/nodemon.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ignore": [ - "locales/*", - "*.log", - "node_modules/*" - ], - "watch": [ - "*.js", - "*.json" - ], - "ext": "js,json" -} \ No newline at end of file diff --git a/Server/openapi.json b/Server/openapi.json deleted file mode 100644 index 7b7bb3cb3..000000000 --- a/Server/openapi.json +++ /dev/null @@ -1,2562 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Checkmate", - "summary": "Checkmate OpenAPI Specifications", - "description": "Checkmate is an open source monitoring tool used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", - "contact": { - "name": "API Support", - "url": "mailto:support@bluewavelabs.ca", - "email": "support@bluewavelabs.ca" - }, - "license": { - "name": "AGPLv3", - "url": "https://github.com/bluewave-labs/checkmate/tree/HEAD/LICENSE" - }, - "version": "1.0" - }, - "servers": [ - { - "url": "http://localhost:{PORT}/{API_PATH}", - "description": "Local Development Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "http://localhost/{API_PATH}", - "description": "Distribution Local Development Server", - "variables": { - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "https://checkmate-demo.bluewavelabs.ca/{API_PATH}", - "description": "Checkmate Demo Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - } - ], - "tags": [ - { - "name": "auth", - "description": "Authentication" - }, - { - "name": "invite", - "description": "Invite" - }, - { - "name": "monitors", - "description": "Monitors" - }, - { - "name": "checks", - "description": "Checks" - }, - { - "name": "maintenance-window", - "description": "Maintenance window" - }, - { - "name": "queue", - "description": "Queue" - }, - { - "name": "status-page", - "description": "Status Page" - } - ], - "paths": { - "/auth/register": { - "post": { - "tags": ["auth"], - "description": "Register a new user", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "firstName", - "lastName", - "email", - "password", - "role", - "teamId" - ], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "teamId": { - "type": "string", - "format": "uuid" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/login": { - "post": { - "tags": ["auth"], - "description": "Login with credentials", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "password"], - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/refresh": { - "post": { - "tags": ["auth"], - "description": "Generates a new auth token if the refresh token is valid.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - }, - "required": false - }, - "parameters": [ - { - "name": "x-refresh-token", - "in": "header", - "description": "Refresh token required to generate a new auth token.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "authorization", - "in": "header", - "description": "Old access token, used to extract payload).", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "New access token generated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "401": { - "description": "Unauthorized or invalid refresh token.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/user/{userId}": { - "put": { - "tags": ["auth"], - "description": "Change user information", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserUpdateRequest" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["auth"], - "description": "Delete user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users/superadmin": { - "get": { - "tags": ["auth"], - "description": "Checks to see if an admin account exists", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users": { - "get": { - "tags": ["auth"], - "description": "Get all users", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/recovery/request": { - "post": { - "tags": ["auth"], - "description": "Request a recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email"], - "properties": { - "email": { - "type": "string", - "format": "email" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/validate": { - "post": { - "tags": ["auth"], - "description": "Validate recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken"], - "properties": { - "recoveryToken": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/reset": { - "post": { - "tags": ["auth"], - "description": "Password reset", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken", "password"], - "properties": { - "recoveryToken": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/invite": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "role"], - "properties": { - "email": { - "type": "string" - }, - "role": { - "type": "array" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/invite/verify": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["token"], - "properties": { - "token": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["monitors"], - "description": "Create a new monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/uptime": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors with uptime stats for 1, 7, 30, and 90 days", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/resolution/url": { - "get": { - "tags": ["monitors"], - "description": "Check DNS resolution for a given URL", - "parameters": [ - { - "name": "monitorURL", - "in": "query", - "required": true, - "schema": { - "type": "string", - "example": "https://example.com" - }, - "description": "The URL to check DNS resolution for" - } - ], - "responses": { - "200": { - "description": "URL resolved successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "400": { - "description": "DNS resolution failed", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "put": { - "tags": ["monitors"], - "description": "Update monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/stats/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor stats", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/certificate/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor certificate", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/summary/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors and summary by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "array", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "description": "Status of monitor, true for up, false for down", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "checkOrder", - "description": "Order of checks", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["asc", "desc"] - } - }, - { - "name": "limit", - "description": "Number of checks to return with monitor", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "type", - "description": "Type of monitor", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "rowsPerPage", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "filter", - "description": "Value to filter by", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "field", - "description": "Field to filter on", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "order", - "description": "Sort order of results", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/pause/{monitorId}": { - "post": { - "tags": ["monitors"], - "description": "Pause monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/demo": { - "post": { - "tags": ["monitors"], - "description": "Create a demo monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/{monitorId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["checks"], - "description": "Create a new check", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCheckBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/{teamId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/ttl": { - "put": { - "tags": ["checks"], - "description": "Update check TTL", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCheckTTLBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/monitor/{monitorId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["maintenance-window"], - "description": "Create maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMaintenanceWindowBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/user/{userId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/jobs": { - "get": { - "tags": ["queue"], - "description": "Get all jobs in queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["queue"], - "description": "Create a new job. Useful for testing scaling workers", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/metrics": { - "get": { - "tags": ["queue"], - "description": "Get queue metrics", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/obliterate": { - "post": { - "tags": ["queue"], - "description": "Obliterate job queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/status-page/{url}": { - "get": { - "tags": ["status-page"], - "description": "Get a status page by URL", - "parameters": [ - { - "name": "url", - "in": "path", - "required": true, - "schema": { "type": "string" } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/SuccessResponse" } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } - } - } - } - } - }, - "post": { - "tags": ["status-page"], - "description": "Create a status page", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateStatusPageBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "400": { - "description": "Duplicate URL", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "ErrorResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": false - }, - "msg": { - "type": "string" - } - } - }, - "SuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": true - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object" - } - } - }, - "UserUpdateRequest": { - "type": "object", - "required": ["firstName", "lastName", "email", "password", "role", "teamId"], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "password": { - "type": "string", - "format": "password" - }, - "newPassword": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "deleteProfileImage": { - "type": "boolean" - } - } - }, - "CreateMonitorBody": { - "type": "object", - "required": ["userId", "teamId", "name", "description", "type", "url"], - "properties": { - "_id": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "teamId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - }, - "url": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "UpdateMonitorBody": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "CreateCheckBody": { - "type": "object", - "required": ["monitorId", "status", "responseTime", "statusCode", "message"], - "properties": { - "monitorId": { - "type": "string" - }, - "status": { - "type": "boolean" - }, - "responseTime": { - "type": "integer" - }, - "statusCode": { - "type": "integer" - }, - "message": { - "type": "string" - } - } - }, - "UpdateCheckTTLBody": { - "type": "object", - "required": ["ttl"], - "properties": { - "ttl": { - "type": "integer" - } - } - }, - "CreateMaintenanceWindowBody": { - "type": "object", - "required": ["userId", "active", "oneTime", "start", "end"], - "properties": { - "userId": { - "type": "string" - }, - "active": { - "type": "boolean" - }, - "oneTime": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "expiry": { - "type": "string", - "format": "date-time" - } - } - }, - "CreateStatusPageBody": { - "type": "object", - "required": ["companyName", "url", "timezone", "color", "theme", "monitors"], - "properties": { - "companyName": { "type": "string" }, - "url": { "type": "string" }, - "timezone": { "type": "string" }, - "color": { "type": "string" }, - "theme": { "type": "string" }, - "monitors": { - "type": "array", - "items": { "type": "string" } - } - } - } - } - } -} diff --git a/Server/package-lock.json b/Server/package-lock.json deleted file mode 100644 index 8b04b093d..000000000 --- a/Server/package-lock.json +++ /dev/null @@ -1,8334 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "server", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "axios": "^1.7.2", - "bcrypt": "5.1.1", - "bullmq": "5.41.2", - "cors": "^2.8.5", - "dockerode": "4.0.4", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "handlebars": "^4.7.8", - "helmet": "^8.0.0", - "ioredis": "^5.4.2", - "jmespath": "^0.16.0", - "joi": "^17.13.1", - "jsonwebtoken": "9.0.2", - "mailersend": "^2.2.0", - "mjml": "^5.0.0-alpha.4", - "mongoose": "^8.3.3", - "multer": "1.4.5-lts.1", - "nodemailer": "^6.9.14", - "ping": "0.4.4", - "sharp": "0.33.5", - "ssl-checker": "2.0.10", - "swagger-ui-express": "5.0.1", - "winston": "^3.13.0" - }, - "devDependencies": { - "@eslint/js": "^9.17.0", - "c8": "10.1.3", - "chai": "5.2.0", - "eslint": "^9.17.0", - "eslint-plugin-mocha": "^10.5.0", - "esm": "3.2.25", - "globals": "^15.14.0", - "mocha": "11.1.0", - "nodemon": "3.1.9", - "prettier": "^3.3.3", - "sinon": "19.0.2" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "license": "MIT", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "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==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "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, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.5", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/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==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/@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==", - "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/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==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "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/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==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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, - "engines": { - "node": ">=18" - }, - "funding": { - "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/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.10.0", - "levn": "^0.4.1" - }, - "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==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "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==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.2.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "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==", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "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 - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "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==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "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==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/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==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "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==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/buildcheck": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", - "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/bullmq": { - "version": "5.41.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.41.2.tgz", - "integrity": "sha512-wqsUIHW2Td86mTKTepqQpKLLUtP4gmX89bUO1YL2fAorxwj3da1GYtroGZMCg/zgB/+zMRsbylL6DHyMUWX7fA==", - "license": "MIT", - "dependencies": { - "cron-parser": "^4.9.0", - "ioredis": "^5.4.1", - "msgpackr": "^1.11.2", - "node-abort-controller": "^3.1.1", - "semver": "^7.5.4", - "tslib": "^2.0.0", - "uuid": "^9.0.0" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/c8": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", - "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.1", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "monocart-coverage-reports": "^2" - }, - "peerDependenciesMeta": { - "monocart-coverage-reports": { - "optional": true - } - } - }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "license": "MIT", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/colorspace/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/colorspace/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/colorspace/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cpu-features": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", - "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "buildcheck": "~0.0.6", - "nan": "^2.19.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.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==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "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==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz", - "integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==", - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^7.0.6", - "lilconfig": "^3.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "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" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "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==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT" - }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "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==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/docker-modem": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", - "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.1.1", - "readable-stream": "^3.5.0", - "split-ca": "^1.0.1", - "ssh2": "^1.15.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.4.tgz", - "integrity": "sha512-6GYP/EdzEY50HaOxTVTJ2p+mB5xDHTMJhS+UoGrVyS6VC+iQRh7kZ4FRpUYq6nziby7hPqWhOrFFUFTMUZJJ5w==", - "license": "Apache-2.0", - "dependencies": { - "@balena/dockerignore": "^1.0.2", - "@grpc/grpc-js": "^1.11.1", - "@grpc/proto-loader": "^0.7.13", - "docker-modem": "^5.0.6", - "protobufjs": "^7.3.2", - "tar-fs": "~2.0.1", - "uuid": "^10.0.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "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==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", - "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", - "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", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "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", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-mocha": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", - "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", - "dev": true, - "dependencies": { - "eslint-utils": "^3.0.0", - "globals": "^13.24.0", - "rambda": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-mocha/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "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", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", - "license": "MIT" - }, - "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, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "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 - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT" - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "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==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "function-bind": "^1.1.2", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC" - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/helmet": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", - "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "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==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^9.0.0", - "posthtml": "^0.16.5", - "timsort": "^0.3.0" - }, - "peerDependencies": { - "cssnano": "^7.0.0", - "postcss": "^8.3.11", - "purgecss": "^6.0.0", - "relateurl": "^0.2.7", - "srcset": "5.0.1", - "svgo": "^3.0.2", - "terser": "^5.10.0", - "uncss": "^0.17.3" - }, - "peerDependenciesMeta": { - "cssnano": { - "optional": true - }, - "postcss": { - "optional": true - }, - "purgecss": { - "optional": true - }, - "relateurl": { - "optional": true - }, - "srcset": { - "optional": true - }, - "svgo": { - "optional": true - }, - "terser": { - "optional": true - }, - "uncss": { - "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/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==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "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==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "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==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-json": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", - "license": "ISC" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "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==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.1", - "unfetch": "^4.2.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "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", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "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 - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "cheerio": "^1.0.0", - "commander": "^12.1.0", - "mensch": "^0.3.4", - "slick": "^1.12.2", - "web-resource-inliner": "^7.0.0" - }, - "bin": { - "juice": "bin/juice" - }, - "engines": { - "node": ">=18.17" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=18.17" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "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==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "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==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", - "license": "MIT", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "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==", - "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==", - "license": "MIT", - "dependencies": { - "gaxios": "^5.0.1", - "isomorphic-unfetch": "^3.1.0", - "qs": "^6.11.0" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" - }, - "node_modules/mensch": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", - "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", - "license": "MIT" - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "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==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mjml": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml/-/mjml-5.0.0-alpha.6.tgz", - "integrity": "sha512-unizId6dKTQSHq1nGnRQqe58kpD7VJu9p+vfMsKO4911/+VCrxkFe0oiwS7Q6XA3rc224MVChO2Mvueyeh16jg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "mjml-cli": "5.0.0-alpha.6", - "mjml-core": "5.0.0-alpha.6", - "mjml-preset-core": "5.0.0-alpha.6", - "mjml-validator": "5.0.0-alpha.6" - }, - "bin": { - "mjml": "bin/mjml" - } - }, - "node_modules/mjml-accordion": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-5.0.0-alpha.6.tgz", - "integrity": "sha512-K7FS8HZQsfUNkvwUWMjCmaEEtHRrqEDddyaVoOE6+ir4H6aWAJUZqp1j/USQsYwMI3YAS3UDpqQEOagG4pRDqg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-body": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-5.0.0-alpha.6.tgz", - "integrity": "sha512-otkINnYsBVbKSYqOz/FmhQruOpYzT10w9+UGcOZdBwv+UqDoKHXAD8QeSFZxr7fDSFhc6qeFAKNSY5uciT1ZuQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-button": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-5.0.0-alpha.6.tgz", - "integrity": "sha512-Z+0J6F2hk7QxKydlABna/vcGtTl7WfMWMLH74x9T1VgNylUAZ2TaWOUAo1AbWICgYBmZfNw1UkDj4EFvb+B1LA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-carousel": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-5.0.0-alpha.6.tgz", - "integrity": "sha512-G2n6D6smGmQEKGByjR52UD14D/0gtVokaEI4wjG7UjisSGYGbDXJW4thfDv2RdwIoozXyGhJD3vRuHsoCPeFnw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-cli": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-5.0.0-alpha.6.tgz", - "integrity": "sha512-mHmw5MCLNImcUUnYwl1yPF3meAd2EApnriMp8DTEN3zCqXsMg1RV8EB1npp4Camwz8HvVMTvEjSoVotwNWxAMg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "chokidar": "^3.0.0", - "glob": "^10.3.10", - "lodash": "^4.17.21", - "minimatch": "^9.0.3", - "mjml-core": "5.0.0-alpha.6", - "mjml-parser-xml": "5.0.0-alpha.6", - "mjml-validator": "5.0.0-alpha.6", - "yargs": "^17.7.2" - }, - "bin": { - "mjml-cli": "bin/mjml" - } - }, - "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", - "integrity": "sha512-FWpmyCH1kzV3g0P6po1OMVOLTgQXDTbo6z30ew+CylUYnn1w5Vh30T1ZvSPYQDc2jGqjsX1zuWO/JUjOJ5B2RQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-core": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-5.0.0-alpha.6.tgz", - "integrity": "sha512-rrGr+xrOCnJ+3V/+LeqA4BCp7jrXiRq0q37FlmSs+etE86yNqMMFMgEFEbeGYTTQO07WuqXlhPSuq85ucAoivg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "cheerio": "1.0.0-rc.12", - "cssnano": "^7.0.1", - "detect-node": "^2.0.4", - "htmlnano": "^2.1.1", - "juice": "^11.0.0", - "lodash": "^4.17.21", - "mjml-parser-xml": "5.0.0-alpha.6", - "mjml-validator": "5.0.0-alpha.6", - "postcss": "^8.4.33", - "prettier": "^3.2.4" - } - }, - "node_modules/mjml-divider": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-5.0.0-alpha.6.tgz", - "integrity": "sha512-lw0rQNn2Y4LRcF/ad4JUjVRsyXhYXdaos6yOXPFjRN8aK1T4p7XctZyP5RMl7m8YfXTNxxyN//hN3bQI5DwvgQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-group": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-5.0.0-alpha.6.tgz", - "integrity": "sha512-No9EeJC9GSpqmxfo0laCmUU0w0xhHb3HaliFJPh7YlzObyRE3nvawHtPYpfNW3lgYNl0U7oYMlN9AQum/DaW2Q==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-5.0.0-alpha.6.tgz", - "integrity": "sha512-owBfZUcwHV2Wjow32QkRNZClbVHIAthacskvowAODUlOfTG1Xj0czlAM6iCG73cSqmZFuocOD9hy47y0l00Qeg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-attributes": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-5.0.0-alpha.6.tgz", - "integrity": "sha512-CW/E+IEw3MOLTWkTMbN6egPwoeg22Iup+fICAH9rmITD6SBJ6f37/MFiACSmpkDQIlWdFokBFFKpikbPWFJKQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-breakpoint": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-5.0.0-alpha.6.tgz", - "integrity": "sha512-3RsHM0l3VU+NhG4MAsXevLH50s/uOJqbZznGVKkgEBsodKDhFK2ndlmUWZ5ywDO6H2g/qDrZse4c1K43uGoyzA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-font": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-5.0.0-alpha.6.tgz", - "integrity": "sha512-+HZ/Ppd/hfFgob9M27Tz9qio/vWwyTwjo40PsYSI6zXKmIUHC5icBcHaezUKMDGadgd+11aXlPLVfs6zGb0hVg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-html-attributes": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-5.0.0-alpha.6.tgz", - "integrity": "sha512-INI3irUFHozLfJCW0pi/499DhlwqpsFEoGQ++NdHlFH87hbQ2XTNlrTHWpgjhSuiJkRJinPYHphRuNyM3KLyVw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-preview": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-5.0.0-alpha.6.tgz", - "integrity": "sha512-HGLcYmJ7q4aYD5VNTUKIvk6wvpXUawI5j4yl9m+DEhk2ptKYmayxLpjiAvH+LioNuPh0zTCGt7U9f5Ja0948gQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-style": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-5.0.0-alpha.6.tgz", - "integrity": "sha512-yQcHIvZGH641irNt21r4hzODp5rEI/qKJRvGVJ6vzyGm7poYbaoxhK7Re9cRu+xZxLpkiTRgf1DYFjyZN45SiQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-head-title": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-5.0.0-alpha.6.tgz", - "integrity": "sha512-m6dGCAItgobZSJ7wlETFPth6rU0+617ZVk1f88Mz5I+YVuOgaAqdNDQWdue5Uj7B19c55NmtdZwsgOKmhHuwow==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-hero": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-5.0.0-alpha.6.tgz", - "integrity": "sha512-OLOKTK/VW4fQOH8yQz1e5kKHfyF0heECghPhmxgnEHigJLbaJy/rmb4x4aq0Lr74Bme8hsMjkR+oktGej1c4iQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-image": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-5.0.0-alpha.6.tgz", - "integrity": "sha512-NENjbEOzobM0iQLlxJUNuqiNlFSP5E1DfiP37bY8smYmpVe4MXi5HC5ajTH5ZBKWsTN4/UVZL7t+ZSqniTnS5w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-navbar": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-5.0.0-alpha.6.tgz", - "integrity": "sha512-bj328dixWJqox9CaA/rvIKsTZ7c5g1mN9B0gL2vuzVMXgX4qnXkzIfN2hw98TdffaKGsKXC6N25iIDVocj1RrQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-parser-xml": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-5.0.0-alpha.6.tgz", - "integrity": "sha512-2gxzFJXBFq6l8f2/HlDq7MBLtWQja0KPSqePxPBOXmNLjqw6bdSflnQu+OhWVptdywuHm907LNIQVIq0wSo2FQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "detect-node": "2.1.0", - "htmlparser2": "^9.1.0", - "lodash": "^4.17.21" - } - }, - "node_modules/mjml-parser-xml/node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, - "node_modules/mjml-preset-core": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-5.0.0-alpha.6.tgz", - "integrity": "sha512-6K9sPXlxQfe9Vx/8/wAFW2Sy6ImWU1AP37VG08Ge7Mj117cNeKk3S1piagbj6LpyTd7E7AueO8G+xzvmC5YBBw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "mjml-accordion": "5.0.0-alpha.6", - "mjml-body": "5.0.0-alpha.6", - "mjml-button": "5.0.0-alpha.6", - "mjml-carousel": "5.0.0-alpha.6", - "mjml-column": "5.0.0-alpha.6", - "mjml-divider": "5.0.0-alpha.6", - "mjml-group": "5.0.0-alpha.6", - "mjml-head": "5.0.0-alpha.6", - "mjml-head-attributes": "5.0.0-alpha.6", - "mjml-head-breakpoint": "5.0.0-alpha.6", - "mjml-head-font": "5.0.0-alpha.6", - "mjml-head-html-attributes": "5.0.0-alpha.6", - "mjml-head-preview": "5.0.0-alpha.6", - "mjml-head-style": "5.0.0-alpha.6", - "mjml-head-title": "5.0.0-alpha.6", - "mjml-hero": "5.0.0-alpha.6", - "mjml-image": "5.0.0-alpha.6", - "mjml-navbar": "5.0.0-alpha.6", - "mjml-raw": "5.0.0-alpha.6", - "mjml-section": "5.0.0-alpha.6", - "mjml-social": "5.0.0-alpha.6", - "mjml-spacer": "5.0.0-alpha.6", - "mjml-table": "5.0.0-alpha.6", - "mjml-text": "5.0.0-alpha.6", - "mjml-wrapper": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-raw": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-5.0.0-alpha.6.tgz", - "integrity": "sha512-tY2P+g7bcydVHrk0NPPm2sd+rIB9l5TdCeIdtw+NSOXoHN7vXVezcdr7UYK8TePibzeymh8w9YTWEAIN8fWvLg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-section": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-5.0.0-alpha.6.tgz", - "integrity": "sha512-gJL+9t9hiAfnXl3gG481Xum1qzURXs6bZ+rZnU47R/070+kinOJTCHt82hFGctjyDi264r/BeM6H6y+m0FIq/w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-social": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-5.0.0-alpha.6.tgz", - "integrity": "sha512-vPRBYHKeEUwEiVwXLrXgQnWBblizdQTlrZ2V45xEH9+3Jqolw3SlTw+uoZzhcFSBNl7+ytdQgwp7gRC7Bn5IYQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-spacer": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-5.0.0-alpha.6.tgz", - "integrity": "sha512-ZyLcoAElvkWnijny2eW0ulX6RxoDQT8AZwv5pJ8O16s0mnfcPAPMaZGTCQZHxPbACKJcCKsIoZrQllQuShf3XA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-table": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-5.0.0-alpha.6.tgz", - "integrity": "sha512-ze7iNRyT6uX099KxZoV33u1jOlibmfxkUUY/8BXbyfWUIgcwRzb1DfKtIWT/GOqI2WAqh5ZwHd5XZgueimttEA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-text": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-5.0.0-alpha.6.tgz", - "integrity": "sha512-k7/pUgwZo9ZwnoCVvohsGdpOrJuLpScH183ZxjyPYO8+kOruSFpE2yrGhK/jeLaV4UhdXndNk8dz80wbN0fKjw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6" - } - }, - "node_modules/mjml-validator": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-5.0.0-alpha.6.tgz", - "integrity": "sha512-kL5IJGYXdNN7VunXXJEWyAP40oODRiSNu5NIx5moOXREPHvn7lRqi/XS8MXa48/UBLmKMbltDSQ0DcnESJbodQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9" - } - }, - "node_modules/mjml-wrapper": { - "version": "5.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-5.0.0-alpha.6.tgz", - "integrity": "sha512-qJisqqkQrtq4U74BTgRaNoPjpf2PwYff/QxH5yvkFDXK3fYEoNt011K7lrm/+u5wS9mx4msHZN0WA4TI/EeeOw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "lodash": "^4.17.21", - "mjml-core": "5.0.0-alpha.6", - "mjml-section": "5.0.0-alpha.6" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mongodb": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz", - "integrity": "sha512-KeESYR5TEaFxOuwRqkOm3XOsMqCSkdeDMjaW5u2nuKfX7rqaofp7JQGoi7sVqQcNJTKuveNbzZtWMstb8ABP6Q==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "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==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^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==", - "license": "MIT", - "dependencies": { - "bson": "^6.10.1", - "kareem": "2.6.3", - "mongodb": "~6.13.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/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "license": "MIT", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "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==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", - "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==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/nise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", - "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.1", - "@sinonjs/text-encoding": "^0.7.3", - "just-extend": "^6.2.0", - "path-to-regexp": "^8.1.0" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "license": "MIT" - }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "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==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "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", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "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", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "license": "MIT", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/ping": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/ping/-/ping-0.4.4.tgz", - "integrity": "sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12 || ^20.9 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.38" - } - }, - "node_modules/postcss-colormin": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz", - "integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^7.0.4" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^5.0.0", - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^5.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "cssnano-utils": "^5.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^5.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.3.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/posthtml": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", - "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", - "license": "MIT", - "dependencies": { - "posthtml-parser": "^0.11.0", - "posthtml-render": "^3.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/posthtml-parser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", - "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", - "license": "MIT", - "dependencies": { - "htmlparser2": "^7.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/posthtml-parser/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/posthtml-parser/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/posthtml-parser/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/posthtml-parser/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/posthtml-parser/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/posthtml-parser/node_modules/htmlparser2": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", - "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.2", - "domutils": "^2.8.0", - "entities": "^3.0.1" - } - }, - "node_modules/posthtml-render": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", - "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", - "license": "MIT", - "dependencies": { - "is-json": "^2.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "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==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rambda": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", - "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "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", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "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==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sinon": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", - "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.2", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "nise": "^6.1.1", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/slick": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", - "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", - "license": "MIT (http://mootools.net/license.txt)", - "engines": { - "node": "*" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", - "license": "ISC" - }, - "node_modules/ssh2": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", - "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", - "hasInstallScript": true, - "dependencies": { - "asn1": "^0.2.6", - "bcrypt-pbkdf": "^1.0.2" - }, - "engines": { - "node": ">=10.16.0" - }, - "optionalDependencies": { - "cpu-features": "~0.0.10", - "nan": "^2.20.0" - } - }, - "node_modules/ssl-checker": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/ssl-checker/-/ssl-checker-2.0.10.tgz", - "integrity": "sha512-SS6rrZocToJWHM1p6iVNb583ybB3UqT1fymCHSWuEdXDUqKA6O1D5Fb8KJVmhj3XKXE82IEWcr+idJrc4jUzFQ==", - "license": "MIT" - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylehacks": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz", - "integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.3", - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.12.0 || ^20.9.0 || >=22.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "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==", - "license": "Apache-2.0", - "dependencies": { - "@scarf/scarf": "=1.4.0" - } - }, - "node_modules/swagger-ui-express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", - "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", - "license": "MIT", - "dependencies": { - "swagger-ui-dist": ">=5.0.0" - }, - "engines": { - "node": ">= v0.10.32" - }, - "peerDependencies": { - "express": ">=4.0.0 || >=5.0.0-beta" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", - "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - }, - "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", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "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", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "license": "Unlicense" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", - "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "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==", - "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" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/valid-data-url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", - "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-resource-inliner": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz", - "integrity": "sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "escape-goat": "^3.0.0", - "htmlparser2": "^5.0.0", - "mime": "^2.4.6", - "valid-data-url": "^3.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/web-resource-inliner/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/dom-serializer/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/domhandler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", - "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.0.1" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/domutils/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/htmlparser2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", - "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^3.3.0", - "domutils": "^2.4.2", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/fb55/htmlparser2?sponsor=1" - } - }, - "node_modules/web-resource-inliner/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wide-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/winston": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", - "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", - "license": "MIT", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.7.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.9.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", - "license": "MIT", - "dependencies": { - "logform": "^2.7.0", - "readable-stream": "^3.6.2", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "license": "MIT" - }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/Server/package.json b/Server/package.json deleted file mode 100644 index d944a1d0c..000000000 --- a/Server/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "server", - "version": "1.0.0", - "description": "", - "main": "index.js", - "type": "module", - "scripts": { - "test": "c8 mocha", - "dev": "nodemon index.js", - "lint": "eslint .", - "lint-fix": "eslint --fix .", - "format": "prettier --write .", - "format-check": "prettier --check ." - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "axios": "^1.7.2", - "bcrypt": "5.1.1", - "bullmq": "5.41.2", - "cors": "^2.8.5", - "dockerode": "4.0.4", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "handlebars": "^4.7.8", - "helmet": "^8.0.0", - "ioredis": "^5.4.2", - "jmespath": "^0.16.0", - "joi": "^17.13.1", - "jsonwebtoken": "9.0.2", - "mailersend": "^2.2.0", - "mjml": "^5.0.0-alpha.4", - "mongoose": "^8.3.3", - "multer": "1.4.5-lts.1", - "nodemailer": "^6.9.14", - "ping": "0.4.4", - "sharp": "0.33.5", - "ssl-checker": "2.0.10", - "swagger-ui-express": "5.0.1", - "winston": "^3.13.0" - }, - "devDependencies": { - "@eslint/js": "^9.17.0", - "c8": "10.1.3", - "chai": "5.2.0", - "eslint": "^9.17.0", - "eslint-plugin-mocha": "^10.5.0", - "esm": "3.2.25", - "globals": "^15.14.0", - "mocha": "11.1.0", - "nodemon": "3.1.9", - "prettier": "^3.3.3", - "sinon": "19.0.2" - } -} diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js deleted file mode 100644 index cebe9a211..000000000 --- a/Server/routes/authRoute.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { isAllowed } from "../middleware/isAllowed.js"; -import multer from "multer"; -import User from "../db/models/User.js"; - -const upload = multer(); - -class AuthRoutes { - constructor(authController) { - this.router = Router(); - this.authController = authController; - this.initRoutes(); - } - - initRoutes() { - this.router.post( - "/register", - upload.single("profileImage"), - this.authController.registerUser - ); - this.router.post("/login", this.authController.loginUser); - this.router.post("/refresh", this.authController.refreshAuthToken); - - this.router.put( - "/user/:userId", - upload.single("profileImage"), - verifyJWT, - this.authController.editUser - ); - - this.router.get("/users/superadmin", this.authController.checkSuperadminExists); - - this.router.get( - "/users", - verifyJWT, - isAllowed(["admin", "superadmin"]), - this.authController.getAllUsers - ); - - this.router.delete( - "/user/:userId", - verifyJWT, - verifyOwnership(User, "userId"), - this.authController.deleteUser - ); - - // Recovery routes - this.router.post("/recovery/request", this.authController.requestRecovery); - this.router.post("/recovery/validate", this.authController.validateRecovery); - this.router.post("/recovery/reset/", this.authController.resetPassword); - } - - getRouter() { - return this.router; - } -} - -export default AuthRoutes; diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js deleted file mode 100644 index bab45e4d4..000000000 --- a/Server/routes/checkRoute.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Router } from "express"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import { isAllowed } from "../middleware/isAllowed.js"; -import Monitor from "../db/models/Monitor.js"; - -class CheckRoutes { - constructor(checkController) { - this.router = Router(); - this.checkController = checkController; - this.initRoutes(); - } - - initRoutes() { - this.router.get("/:monitorId", this.checkController.getChecksByMonitor); - this.router.post( - "/:monitorId", - verifyOwnership(Monitor, "monitorId"), - this.checkController.createCheck - ); - this.router.delete( - "/:monitorId", - verifyOwnership(Monitor, "monitorId"), - this.checkController.deleteChecks - ); - - this.router.get("/team/:teamId", this.checkController.getChecksByTeam); - - this.router.delete( - "/team/:teamId", - isAllowed(["admin", "superadmin"]), - this.checkController.deleteChecksByTeamId - ); - - this.router.put( - "/team/ttl", - isAllowed(["admin", "superadmin"]), - this.checkController.updateChecksTTL - ); - } - - getRouter() { - return this.router; - } -} - -export default CheckRoutes; diff --git a/Server/routes/distributedUptimeRoute.js b/Server/routes/distributedUptimeRoute.js deleted file mode 100644 index 3fbd88d3a..000000000 --- a/Server/routes/distributedUptimeRoute.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Router } from "express"; - -class DistributedUptimeRoutes { - constructor(distributedUptimeController) { - this.router = Router(); - this.distributedUptimeController = distributedUptimeController; - this.initRoutes(); - } - initRoutes() { - this.router.post("/callback", this.distributedUptimeController.resultsCallback); - this.router.get( - "/monitors/:teamId", - this.distributedUptimeController.getDistributedUptimeMonitors - ); - this.router.get( - "/monitors/details/:monitorId", - this.distributedUptimeController.getDistributedUptimeMonitorDetails - ); - } - - getRouter() { - return this.router; - } -} - -export default DistributedUptimeRoutes; diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js deleted file mode 100644 index c66c573f1..000000000 --- a/Server/routes/inviteRoute.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; - -class InviteRoutes { - constructor(inviteController) { - this.router = Router(); - this.inviteController = inviteController; - this.initRoutes(); - } - - initRoutes() { - this.router.post( - "/", - isAllowed(["admin", "superadmin"]), - verifyJWT, - this.inviteController.issueInvitation - ); - this.router.post("/verify", this.inviteController.inviteVerifyController); - } - - getRouter() { - return this.router; - } -} - -export default InviteRoutes; diff --git a/Server/routes/maintenanceWindowRoute.js b/Server/routes/maintenanceWindowRoute.js deleted file mode 100644 index 2665c2bb8..000000000 --- a/Server/routes/maintenanceWindowRoute.js +++ /dev/null @@ -1,37 +0,0 @@ -import { Router } from "express"; -import { verifyOwnership } from "../middleware/verifyOwnership.js"; -import Monitor from "../db/models/Monitor.js"; - -class MaintenanceWindowRoutes { - constructor(maintenanceWindowController) { - this.router = Router(); - this.maintenanceWindowController = maintenanceWindowController; - this.initRoutes(); - } - initRoutes() { - this.router.post("/", this.maintenanceWindowController.createMaintenanceWindows); - - this.router.get( - "/monitor/:monitorId", - verifyOwnership(Monitor, "monitorId"), - this.maintenanceWindowController.getMaintenanceWindowsByMonitorId - ); - - this.router.get( - "/team/", - this.maintenanceWindowController.getMaintenanceWindowsByTeamId - ); - - this.router.get("/:id", this.maintenanceWindowController.getMaintenanceWindowById); - - this.router.put("/:id", this.maintenanceWindowController.editMaintenanceWindow); - - this.router.delete("/:id", this.maintenanceWindowController.deleteMaintenanceWindow); - } - - getRouter() { - return this.router; - } -} - -export default MaintenanceWindowRoutes; diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js deleted file mode 100644 index 964bbee7a..000000000 --- a/Server/routes/monitorRoute.js +++ /dev/null @@ -1,87 +0,0 @@ -import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; -import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; - -class MonitorRoutes { - constructor(monitorController) { - this.router = Router(); - this.monitorController = monitorController; - this.initRoutes(); - } - - initRoutes() { - this.router.get("/", this.monitorController.getAllMonitors); - this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); - this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); - this.router.get( - "/hardware/details/:monitorId", - this.monitorController.getHardwareDetailsById - ); - - this.router.get( - "/uptime/details/:monitorId", - this.monitorController.getUptimeDetailsById - ); - this.router.get("/certificate/:monitorId", (req, res, next) => { - this.monitorController.getMonitorCertificate( - req, - res, - next, - fetchMonitorCertificate - ); - }); - this.router.get("/:monitorId", this.monitorController.getMonitorById); - - this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId); - - this.router.get( - "/resolution/url", - isAllowed(["admin", "superadmin"]), - this.monitorController.checkEndpointResolution - ); - - this.router.delete( - "/:monitorId", - isAllowed(["admin", "superadmin"]), - this.monitorController.deleteMonitor - ); - - this.router.post( - "/", - isAllowed(["admin", "superadmin"]), - this.monitorController.createMonitor - ); - - this.router.put( - "/:monitorId", - isAllowed(["admin", "superadmin"]), - this.monitorController.editMonitor - ); - - this.router.delete( - "/", - isAllowed(["superadmin"]), - this.monitorController.deleteAllMonitors - ); - - this.router.post( - "/pause/:monitorId", - isAllowed(["admin", "superadmin"]), - this.monitorController.pauseMonitor - ); - - this.router.post( - "/demo", - isAllowed(["admin", "superadmin"]), - this.monitorController.addDemoMonitors - ); - - this.router.post("/seed", isAllowed(["superadmin"]), this.monitorController.seedDb); - } - - getRouter() { - return this.router; - } -} - -export default MonitorRoutes; diff --git a/Server/routes/notificationRoute.js b/Server/routes/notificationRoute.js deleted file mode 100644 index 6e13a53d1..000000000 --- a/Server/routes/notificationRoute.js +++ /dev/null @@ -1,24 +0,0 @@ -import express from 'express'; -import { verifyJWT } from '../middleware/verifyJWT.js'; - -class NotificationRoutes { - constructor(notificationController) { - this.notificationController = notificationController; - this.router = express.Router(); - this.initializeRoutes(); - } - - initializeRoutes() { - this.router.post( - '/trigger', - verifyJWT, - this.notificationController.triggerNotification - ); - } - - getRouter() { - return this.router; - } -} - -export default NotificationRoutes; \ No newline at end of file diff --git a/Server/routes/queueRoute.js b/Server/routes/queueRoute.js deleted file mode 100644 index af0e9d08e..000000000 --- a/Server/routes/queueRoute.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Router } from "express"; - -class QueueRoutes { - constructor(queueController) { - this.router = Router(); - this.queueController = queueController; - this.initRoutes(); - } - initRoutes() { - this.router.get("/metrics", this.queueController.getMetrics); - this.router.get("/jobs", this.queueController.getJobs); - this.router.post("/jobs", this.queueController.addJob); - this.router.post("/obliterate", this.queueController.obliterateQueue); - } - - getRouter() { - return this.router; - } -} - -export default QueueRoutes; diff --git a/Server/routes/settingsRoute.js b/Server/routes/settingsRoute.js deleted file mode 100644 index 8da262427..000000000 --- a/Server/routes/settingsRoute.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Router } from "express"; -import { isAllowed } from "../middleware/isAllowed.js"; - -class SettingsRoutes { - constructor(settingsController) { - this.router = Router(); - this.settingsController = settingsController; - this.initRoutes(); - } - - initRoutes() { - this.router.get("/", this.settingsController.getAppSettings); - this.router.put( - "/", - isAllowed(["superadmin"]), - this.settingsController.updateAppSettings - ); - } - - getRouter() { - return this.router; - } -} - -export default SettingsRoutes; diff --git a/Server/routes/statusPageRoute.js b/Server/routes/statusPageRoute.js deleted file mode 100644 index 3ed6e5684..000000000 --- a/Server/routes/statusPageRoute.js +++ /dev/null @@ -1,38 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import multer from "multer"; -const upload = multer(); - -class StatusPageRoutes { - constructor(statusPageController) { - this.router = Router(); - this.statusPageController = statusPageController; - this.initRoutes(); - } - - initRoutes() { - this.router.get("/", this.statusPageController.getStatusPage); - this.router.get("/team/:teamId", this.statusPageController.getStatusPagesByTeamId); - this.router.get("/:url", this.statusPageController.getStatusPageByUrl); - - this.router.post( - "/", - upload.single("logo"), - verifyJWT, - this.statusPageController.createStatusPage - ); - this.router.put( - "/", - upload.single("logo"), - verifyJWT, - this.statusPageController.updateStatusPage - ); - this.router.delete("/:url(*)", verifyJWT, this.statusPageController.deleteStatusPage); - } - - getRouter() { - return this.router; - } -} - -export default StatusPageRoutes; diff --git a/Server/service/emailService.js b/Server/service/emailService.js deleted file mode 100644 index 3aeb2c8a8..000000000 --- a/Server/service/emailService.js +++ /dev/null @@ -1,140 +0,0 @@ -import { fileURLToPath } from "url"; -import path from "path"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const SERVICE_NAME = "EmailService"; - -/** - * Represents an email service that can load templates, build, and send emails. - */ -class EmailService { - static SERVICE_NAME = SERVICE_NAME; - /** - * Constructs an instance of the EmailService, initializing template loaders and the email transporter. - * @param {Object} settingsService - The settings service to get email configuration. - * @param {Object} fs - The file system module. - * @param {Object} path - The path module. - * @param {Function} compile - The Handlebars compile function. - * @param {Function} mjml2html - The MJML to HTML conversion function. - * @param {Object} nodemailer - The nodemailer module. - * @param {Object} logger - The logger module. - */ - constructor(settingsService, fs, path, compile, mjml2html, nodemailer, logger) { - this.settingsService = settingsService; - this.fs = fs; - this.path = path; - this.compile = compile; - this.mjml2html = mjml2html; - this.nodemailer = nodemailer; - this.logger = logger; - - /** - * Loads an email template from the filesystem. - * - * @param {string} templateName - The name of the template to load. - * @returns {Function} A compiled template function that can be used to generate HTML email content. - */ - this.loadTemplate = (templateName) => { - try { - const templatePath = this.path.join( - __dirname, - `../templates/${templateName}.mjml` - ); - const templateContent = this.fs.readFileSync(templatePath, "utf8"); - return this.compile(templateContent); - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "loadTemplate", - stack: error.stack, - }); - } - }; - - /** - * A lookup object to access preloaded email templates. - * @type {Object.} - * TODO Load less used templates in their respective functions - */ - this.templateLookup = { - welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), - employeeActivationTemplate: this.loadTemplate("employeeActivation"), - noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), - serverIsDownTemplate: this.loadTemplate("serverIsDown"), - serverIsUpTemplate: this.loadTemplate("serverIsUp"), - passwordResetTemplate: this.loadTemplate("passwordReset"), - hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), - }; - - /** - * The email transporter used to send emails. - * @type {Object} - */ - - const { systemEmailHost, systemEmailPort, systemEmailAddress, systemEmailPassword } = - this.settingsService.getSettings(); - - const emailConfig = { - host: systemEmailHost, - port: systemEmailPort, - secure: true, - auth: { - user: systemEmailAddress, - pass: systemEmailPassword, - }, - }; - - this.transporter = this.nodemailer.createTransport(emailConfig); - } - - /** - * Asynchronously builds and sends an email using a specified template and context. - * - * @param {string} template - The name of the template to use for the email body. - * @param {Object} context - The data context to render the template with. - * @param {string} to - The recipient's email address. - * @param {string} subject - The subject of the email. - * @returns {Promise} A promise that resolves to the messageId of the sent email. - */ - buildAndSendEmail = async (template, context, to, subject) => { - const buildHtml = async (template, context) => { - try { - const mjml = this.templateLookup[template](context); - const html = await this.mjml2html(mjml); - return html.html; - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "buildAndSendEmail", - stack: error.stack, - }); - } - }; - - const sendEmail = async (to, subject, html) => { - try { - const info = await this.transporter.sendMail({ - to: to, - subject: subject, - html: html, - }); - return info; - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "sendEmail", - stack: error.stack, - }); - } - }; - const html = await buildHtml(template, context); - const info = await sendEmail(to, subject, html); - return info?.messageId; - }; -} -export default EmailService; diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js deleted file mode 100644 index be36383d2..000000000 --- a/Server/service/jobQueue.js +++ /dev/null @@ -1,606 +0,0 @@ -const QUEUE_NAMES = ["uptime", "pagespeed", "hardware", "distributed"]; -const SERVICE_NAME = "JobQueue"; -const JOBS_PER_WORKER = 5; -const QUEUE_LOOKUP = { - hardware: "hardware", - http: "uptime", - ping: "uptime", - port: "uptime", - docker: "uptime", - pagespeed: "pagespeed", - distributed_http: "distributed", -}; -const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`; - - -class NewJobQueue { - static SERVICE_NAME = SERVICE_NAME; - - constructor( - db, - statusService, - networkService, - notificationService, - settingsService, - stringService, - logger, - Queue, - Worker - ) { - const settings = settingsService.getSettings() || {}; - const { redisHost = "127.0.0.1", redisPort = 6379 } = settings; - const connection = { - host: redisHost, - port: redisPort, - }; - - this.queues = {}; - this.workers = {}; - - this.connection = connection; - this.db = db; - this.networkService = networkService; - this.statusService = statusService; - this.notificationService = notificationService; - this.settingsService = settingsService; - this.logger = logger; - this.Worker = Worker; - this.stringService = stringService; - - QUEUE_NAMES.forEach((name) => { - this.queues[name] = new Queue(name, { connection }); - this.workers[name] = []; - }); - } - - /** - * Initializes job queues by adding jobs for all active monitors - * @async - * @function initJobQueue - * @description Retrieves all monitors from the database and adds jobs for active ones to their respective queues - * @throws {Error} If there's an error retrieving monitors or adding jobs - * @returns {Promise} - */ - async initJobQueue() { - const monitors = await this.db.getAllMonitors(); - await Promise.all( - monitors - .filter((monitor) => monitor.isActive) - .map(async (monitor) => { - try { - await this.addJob(monitor._id, monitor); - } catch (error) { - this.logger.error({ - message: `Failed to add job for monitor ${monitor._id}:`, - service: SERVICE_NAME, - method: "initJobQueue", - stack: error.stack, - }); - } - }) - ); - } - - /** - * Checks if a monitor is currently in a maintenance window - * @async - * @param {string} monitorId - The ID of the monitor to check - * @returns {Promise} Returns true if the monitor is in an active maintenance window, false otherwise - * @throws {Error} If there's an error retrieving maintenance windows from the database - * @description - * Retrieves all maintenance windows for a monitor and checks if any are currently active. - * A maintenance window is considered active if: - * 1. The window is marked as active AND - * 2. Either: - * - Current time falls between start and end times - * - For repeating windows: Current time falls between any repeated interval - */ - 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; - } - - /** - * Creates a job processing handler for monitor checks - * @function createJobHandler - * @returns {Function} An async function that processes monitor check jobs - * @description - * Creates and returns a job handler that: - * 1. Checks if monitor is in maintenance window - * 2. If not in maintenance, performs network status check - * 3. Updates monitor status in database - * 4. Triggers notifications if status changed - * - * @param {Object} job - The job to process - * @param {Object} job.data - The monitor data - * @param {string} job.data._id - Monitor ID - * @param {string} job.id - Job ID - * - * @throws {Error} Logs errors but doesn't throw them to prevent job failure - * @returns {Promise} Resolves when job processing is complete - */ - createJobHandler() { - return async (job) => { - try { - // Get all maintenance windows for this monitor - await job.updateProgress(0); - const monitorId = job.data._id; - const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId); - // If a maintenance window is active, we're done - - if (maintenanceWindowActive) { - await job.updateProgress(100); - this.logger.info({ - message: `Monitor ${monitorId} is in maintenance window`, - service: SERVICE_NAME, - method: "createWorker", - }); - return false; - } - - // Get the current status - await job.updateProgress(30); - const networkResponse = await this.networkService.getStatus(job); - if (job.data.type === "distributed_http") { - return; - } - // Handle status change - await job.updateProgress(60); - const { monitor, statusChanged, prevStatus } = - await this.statusService.updateStatus(networkResponse); - // Handle notifications - await job.updateProgress(80); - this.notificationService - .handleNotifications({ - ...networkResponse, - monitor, - prevStatus, - statusChanged, - }) - .catch((error) => { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "createJobHandler", - details: `Error sending notifications for job ${job.id}: ${error.message}`, - stack: error.stack, - }); - }); - await job.updateProgress(100); - return true; - } catch (error) { - this.logger.error({ - message: error.message, - service: error.service ?? SERVICE_NAME, - method: error.method ?? "createJobHandler", - details: `Error processing job ${job.id}: ${error.message}`, - stack: error.stack, - }); - throw error; - } - }; - } - - /** - * Creates a new worker for processing jobs in a queue - * @param {Queue} queue - The BullMQ queue to create a worker for - * @returns {Worker} A new BullMQ worker instance - * @description - * Creates and configures a new worker with: - * - Queue-specific job handler - * - Redis connection settings - * - Default worker options - * The worker processes jobs from the specified queue using the job handler - * created by createJobHandler() - * - * @throws {Error} If worker creation fails or connection is invalid - */ - createWorker(queue) { - const worker = new this.Worker(queue.name, this.createJobHandler(), { - connection: this.connection, - concurrency: 5, - }); - return worker; - } - - /** - * Gets stats related to the workers - * This is used for scaling workers right now - * In the future we will likely want to scale based on server performance metrics - * CPU Usage & memory usage, if too high, scale down workers. - * When to scale up? If jobs are taking too long to complete? - * @async - * @returns {Promise} - Returns the worker stats - */ - async getWorkerStats(queue) { - try { - const jobs = await queue.getRepeatableJobs(); - const load = jobs.length / this.workers[queue.name].length; - return { jobs, load }; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getWorkerStats") : null; - throw error; - } - } - - /** - * Scales workers up or down based on queue load - * @async - * @param {Object} workerStats - Statistics about current worker load - * @param {number} workerStats.load - Current load per worker - * @param {Array} workerStats.jobs - Array of current jobs - * @param {Queue} queue - The BullMQ queue to scale workers for - * @returns {Promise} True if scaling occurred, false if no scaling was needed - * @throws {Error} If no workers array exists for the queue - * @description - * Scales workers based on these rules: - * - Maintains minimum of 5 workers - * - Adds workers if load exceeds JOBS_PER_WORKER - * - Removes workers if load is below JOBS_PER_WORKER - * - Creates initial workers if none exist - * Worker scaling is calculated based on excess jobs or excess capacity - */ - async scaleWorkers(workerStats, queue) { - const workers = this.workers[queue.name]; - if (workers === undefined) { - throw new Error(`No workers found for ${queue.name}`); - } - - if (workers.length === 0) { - // There are no workers, need to add one - for (let i = 0; i < 5; i++) { - const worker = this.createWorker(queue); - workers.push(worker); - } - return true; - } - if (workerStats.load > JOBS_PER_WORKER) { - // Find out how many more jobs we have than current workers can handle - const excessJobs = workerStats.jobs.length - workers.length * JOBS_PER_WORKER; - // Divide by jobs/worker to find out how many workers to add - const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER); - for (let i = 0; i < workersToAdd; i++) { - const worker = this.createWorker(queue); - workers.push(worker); - } - return true; - } - - if (workerStats.load < JOBS_PER_WORKER) { - // Find out how much excess capacity we have - const workerCapacity = workers.length * JOBS_PER_WORKER; - const excessCapacity = workerCapacity - workerStats.jobs.length; - // Calculate how many workers to remove - let workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER); // Make sure there are always at least 5 - while (workersToRemove > 0 && workers.length > 5) { - const worker = workers.pop(); - workersToRemove--; - await worker.close().catch((error) => { - // Catch the error instead of throwing it - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "scaleWorkers", - stack: error.stack, - }); - }); - } - return true; - } - return false; - } - - /** - * Gets all jobs in the queue. - * - * @async - * @returns {Promise>} - * @throws {Error} - Throws error if getting jobs fails - */ - async getJobs(queue) { - try { - const jobs = await queue.getRepeatableJobs(); - return jobs; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getJobs") : null; - throw error; - } - } - - /** - * Retrieves detailed statistics about jobs and workers for all queues - * @async - * @returns {Promise} Queue statistics object - * @throws {Error} If there's an error retrieving job information - * @description - * Returns an object with statistics for each queue including: - * - List of jobs with their URLs and current states - * - Number of workers assigned to the queue - */ - async getJobStats() { - try { - let stats = {}; - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const queue = this.queues[name]; - const jobs = await queue.getJobs(); - const ret = await Promise.all( - jobs.map(async (job) => { - const state = await job.getState(); - return { url: job.data.url, state, progress: job.progress }; - }) - ); - stats[name] = { jobs: ret, workers: this.workers[name].length }; - }) - ); - return stats; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getJobStats") : null; - throw error; - } - } - - /** - * Adds both immediate and repeatable jobs to the appropriate queue - * @async - * @param {string} jobName - Name identifier for the job - * @param {Object} monitor - Job data and configuration - * @param {string} monitor.type - Type of monitor/queue ('uptime', 'pagespeed', 'hardware') - * @param {string} [monitor.url] - URL to monitor (optional) - * @param {number} [monitor.interval=60000] - Repeat interval in milliseconds - * @param {string} monitor._id - Monitor ID - * @throws {Error} If queue not found for payload type - * @throws {Error} If job addition fails - * @description - * 1. Identifies correct queue based on payload type - * 2. Adds immediate job execution - * 3. Adds repeatable job with specified interval - * 4. Scales workers based on updated queue load - * Jobs are configured with exponential backoff, single attempt, - * and automatic removal on completion - */ - async addJob(jobName, monitor) { - try { - this.logger.info({ - message: `Adding job ${monitor?.url ?? "No URL"}`, - service: SERVICE_NAME, - method: "addJob", - }); - - // Find the correct queue - - const queue = this.queues[QUEUE_LOOKUP[monitor.type]]; - if (queue === undefined) { - throw new Error(`Queue for ${monitor.type} not found`); - } - - const jobTemplate = { - name: jobName, - data: monitor, - opts: { - attempts: 1, - backoff: { - type: "exponential", - delay: 1000, - }, - removeOnComplete: true, - removeOnFail: false, - timeout: 1 * 60 * 1000, - }, - }; - - const schedulerId = getSchedulerId(monitor); - await queue.upsertJobScheduler( - schedulerId, - { every: monitor?.interval ?? 60000 }, - jobTemplate - ); - - const workerStats = await this.getWorkerStats(queue); - await this.scaleWorkers(workerStats, queue); - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "addJob") : null; - throw error; - } - } - - /** - * Deletes a repeatable job from its queue and adjusts worker scaling - * @async - * @param {Object} monitor - Monitor object containing job details - * @param {string} monitor._id - ID of the monitor/job to delete - * @param {string} monitor.type - Type of monitor determining queue selection - * @param {number} monitor.interval - Job repeat interval in milliseconds - * @throws {Error} If queue not found for monitor type - * @throws {Error} If job deletion fails - * @description - * 1. Identifies correct queue based on monitor type - * 2. Removes repeatable job using monitor ID and interval - * 3. Logs success or failure of deletion - * 4. Updates worker scaling based on new queue load - * Returns void but logs operation result - */ - async deleteJob(monitor) { - try { - const queue = this.queues[QUEUE_LOOKUP[monitor.type]]; - const schedulerId = getSchedulerId(monitor); - const wasDeleted = await queue.removeJobScheduler(schedulerId); - - if (wasDeleted === true) { - this.logger.info({ - message: this.stringService.jobQueueDeleteJob, - service: SERVICE_NAME, - method: "deleteJob", - details: `Deleted job ${monitor._id}`, - }); - const workerStats = await this.getWorkerStats(queue); - await this.scaleWorkers(workerStats, queue); - } else { - this.logger.error({ - message: this.stringService.jobQueueDeleteJob, - service: SERVICE_NAME, - method: "deleteJob", - details: `Failed to delete job ${monitor._id}`, - }); - } - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "deleteJob") : null; - throw error; - } - } - - /** - * Retrieves comprehensive metrics for all queues - * @async - * @returns {Promise>} Object with metrics for each queue - * @throws {Error} If metrics retrieval fails - * @description - * Collects the following metrics for each queue: - * - Number of waiting jobs - * - Number of active jobs - * - Number of completed jobs - * - Number of failed jobs - * - Number of delayed jobs - * - Number of repeatable jobs - * - Number of active workers - * - * @typedef {Object} QueueMetrics - * @property {number} waiting - Count of jobs waiting to be processed - * @property {number} active - Count of jobs currently being processed - * @property {number} completed - Count of successfully completed jobs - * @property {number} failed - Count of failed jobs - * @property {number} delayed - Count of delayed jobs - * @property {number} repeatableJobs - Count of repeatable job patterns - * @property {number} workers - Count of active workers for this queue - */ - async getMetrics() { - try { - let metrics = {}; - - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const queue = this.queues[name]; - const workers = this.workers[name]; - const [waiting, active, completed, failed, delayed, repeatableJobs] = - await Promise.all([ - queue.getWaitingCount(), - queue.getActiveCount(), - queue.getCompletedCount(), - queue.getFailedCount(), - queue.getDelayedCount(), - queue.getRepeatableJobs(), - ]); - - metrics[name] = { - waiting, - active, - completed, - failed, - delayed, - repeatableJobs: repeatableJobs.length, - workers: workers.length, - }; - }) - ); - - return metrics; - } catch (error) { - this.logger.error({ - message: error.message, - service: SERVICE_NAME, - method: "getMetrics", - stack: error.stack, - }); - } - } - - /** - * @async - * @returns {Promise} - Returns true if obliteration is successful - */ - - async obliterate() { - try { - this.logger.info({ - message: "Attempting to obliterate job queue...", - service: SERVICE_NAME, - method: "obliterate", - }); - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const queue = this.queues[name]; - await queue.pause(); - const jobs = await this.getJobs(queue); - - // Remove all repeatable jobs - for (const job of jobs) { - await queue.removeRepeatableByKey(job.key); - await queue.remove(job.id); - } - }) - ); - - // Close workers - await Promise.all( - QUEUE_NAMES.map(async (name) => { - const workers = this.workers[name]; - await Promise.all( - workers.map(async (worker) => { - await worker.close(); - }) - ); - }) - ); - - QUEUE_NAMES.forEach(async (name) => { - const queue = this.queues[name]; - await queue.obliterate(); - }); - - const metrics = await this.getMetrics(); - this.logger.info({ - message: this.stringService.jobQueueObliterate, - service: SERVICE_NAME, - method: "obliterate", - details: metrics, - }); - return true; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "obliterate") : null; - throw error; - } - } -} - -export default NewJobQueue; diff --git a/Server/service/networkService.js b/Server/service/networkService.js deleted file mode 100644 index 984939bd4..000000000 --- a/Server/service/networkService.js +++ /dev/null @@ -1,509 +0,0 @@ -import jmespath from "jmespath"; -const SERVICE_NAME = "NetworkService"; -const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push"; - -/** - * Constructs a new NetworkService instance. - * - * @param {Object} axios - The axios instance for HTTP requests. - * @param {Object} ping - The ping utility for network checks. - * @param {Object} logger - The logger instance for logging. - * @param {Object} http - The HTTP utility for network operations. - * @param {Object} net - The net utility for network operations. - */ -class NetworkService { - static SERVICE_NAME = SERVICE_NAME; - - constructor(axios, ping, logger, http, Docker, net, stringService) { - this.TYPE_PING = "ping"; - this.TYPE_HTTP = "http"; - this.TYPE_PAGESPEED = "pagespeed"; - this.TYPE_HARDWARE = "hardware"; - this.TYPE_DOCKER = "docker"; - this.TYPE_PORT = "port"; - this.TYPE_DISTRIBUTED_HTTP = "distributed_http"; - this.SERVICE_NAME = SERVICE_NAME; - this.NETWORK_ERROR = 5000; - this.PING_ERROR = 5001; - this.axios = axios; - this.ping = ping; - this.logger = logger; - this.http = http; - this.Docker = Docker; - this.net = net; - this.stringService = stringService; - } - - /** - * Times the execution of an asynchronous operation. - * - * @param {Function} operation - The asynchronous operation to be timed. - * @returns {Promise} An object containing the response, response time, and optionally an error. - * @property {Object|null} response - The response from the operation, or null if an error occurred. - * @property {number} responseTime - The time taken for the operation to complete, in milliseconds. - * @property {Error} [error] - The error object if an error occurred during the operation. - */ - async timeRequest(operation) { - const startTime = Date.now(); - try { - const response = await operation(); - const endTime = Date.now(); - const responseTime = endTime - startTime; - return { response, responseTime }; - } catch (error) { - const endTime = Date.now(); - const responseTime = endTime - startTime; - return { response: null, responseTime, error }; - } - } - - /** - * Sends a ping request to the specified URL and returns the response. - * - * @param {Object} job - The job object containing the data for the ping request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.url - The URL to ping. - * @param {string} job.data._id - The monitor ID for the ping request. - * @returns {Promise} An object containing the ping response details. - * @property {string} monitorId - The monitor ID for the ping request. - * @property {string} type - The type of request, which is "ping". - * @property {number} responseTime - The time taken for the ping request to complete, in milliseconds. - * @property {Object} payload - The response payload from the ping request. - * @property {boolean} status - The status of the ping request (true if successful, false otherwise). - * @property {number} code - The response code (200 if successful, error code otherwise). - * @property {string} message - The message indicating the result of the ping request. - */ - async requestPing(job) { - try { - const url = job.data.url; - const { response, responseTime, error } = await this.timeRequest(() => - this.ping.promise.probe(url) - ); - - const pingResponse = { - monitorId: job.data._id, - type: "ping", - responseTime, - payload: response, - }; - if (error) { - pingResponse.status = false; - pingResponse.code = this.PING_ERROR; - pingResponse.message = "No response"; - return pingResponse; - } - - pingResponse.code = 200; - pingResponse.status = response.alive; - pingResponse.message = "Success"; - return pingResponse; - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestPing"; - throw error; - } - } - - /** - * Sends an HTTP GET request to the specified URL and returns the response. - * - * @param {Object} job - The job object containing the data for the HTTP request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.url - The URL to send the HTTP GET request to. - * @param {string} job.data._id - The monitor ID for the HTTP request. - * @param {string} [job.data.secret] - Secret for authorization if provided. - * @returns {Promise} An object containing the HTTP response details. - * @property {string} monitorId - The monitor ID for the HTTP request. - * @property {string} type - The type of request, which is "http". - * @property {number} responseTime - The time taken for the HTTP request to complete, in milliseconds. - * @property {Object} payload - The response payload from the HTTP request. - * @property {boolean} status - The status of the HTTP request (true if successful, false otherwise). - * @property {number} code - The response code (200 if successful, error code otherwise). - * @property {string} message - The message indicating the result of the HTTP request. - */ - async requestHttp(job) { - try { - const { - url, - secret, - _id, - name, - teamId, - type, - jsonPath, - matchMethod, - expectedValue, - } = job.data; - const config = {}; - - secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` }); - - const { response, responseTime, error } = await this.timeRequest(() => - this.axios.get(url, config) - ); - - const httpResponse = { - monitorId: _id, - teamId, - type, - responseTime, - payload: response?.data, - }; - - if (error) { - const code = error.response?.status || this.NETWORK_ERROR; - httpResponse.code = code; - httpResponse.status = false; - httpResponse.message = - this.http.STATUS_CODES[code] || this.stringService.httpNetworkError; - return httpResponse; - } - - httpResponse.code = response.status; - - if (!expectedValue) { - // not configure expected value, return - httpResponse.status = true; - httpResponse.message = this.http.STATUS_CODES[response.status]; - return httpResponse; - } - - // validate if response data match expected value - let result = response?.data; - - this.logger.info({ - service: this.SERVICE_NAME, - method: "requestHttp", - message: `Job: [${name}](${_id}) match result with expected value`, - details: { expectedValue, result, jsonPath, matchMethod }, - }); - - if (jsonPath) { - const contentType = response.headers["content-type"]; - - const isJson = contentType?.includes("application/json"); - if (!isJson) { - httpResponse.status = false; - httpResponse.message = this.stringService.httpNotJson; - return httpResponse; - } - - try { - result = jmespath.search(result, jsonPath); - } catch (error) { - httpResponse.status = false; - httpResponse.message = this.stringService.httpJsonPathError; - return httpResponse; - } - } - - if (result === null || result === undefined) { - httpResponse.status = false; - httpResponse.message = this.stringService.httpEmptyResult; - return httpResponse; - } - - let match; - result = typeof result === "object" ? JSON.stringify(result) : result.toString(); - if (matchMethod === "include") match = result.includes(expectedValue); - else if (matchMethod === "regex") match = new RegExp(expectedValue).test(result); - else match = result === expectedValue; - - httpResponse.status = match; - httpResponse.message = match - ? this.stringService.httpMatchSuccess - : this.stringService.httpMatchFail; - return httpResponse; - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestHttp"; - throw error; - } - } - - /** - * Sends a request to the Google PageSpeed Insights API for the specified URL and returns the response. - * - * @param {Object} job - The job object containing the data for the PageSpeed request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.url - The URL to analyze with PageSpeed Insights. - * @param {string} job.data._id - The monitor ID for the PageSpeed request. - * @returns {Promise} An object containing the PageSpeed response details. - * @property {string} monitorId - The monitor ID for the PageSpeed request. - * @property {string} type - The type of request, which is "pagespeed". - * @property {number} responseTime - The time taken for the PageSpeed request to complete, in milliseconds. - * @property {Object} payload - The response payload from the PageSpeed request. - * @property {boolean} status - The status of the PageSpeed request (true if successful, false otherwise). - * @property {number} code - The response code (200 if successful, error code otherwise). - * @property {string} message - The message indicating the result of the PageSpeed request. - */ - async requestPagespeed(job) { - try { - const url = job.data.url; - const updatedJob = { ...job }; - const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; - updatedJob.data.url = pagespeedUrl; - return await this.requestHttp(updatedJob); - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestPagespeed"; - throw error; - } - } - - /** - * Sends an HTTP request to check hardware status and returns the response. - * - * @param {Object} job - The job object containing the data for the hardware request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.url - The URL to send the hardware status request to. - * @param {string} job.data._id - The monitor ID for the hardware request. - * @param {string} job.data.type - The type of request, which is "hardware". - * @returns {Promise} An object containing the hardware status response details. - * @property {string} monitorId - The monitor ID for the hardware request. - * @property {string} type - The type of request ("hardware"). - * @property {number} responseTime - The time taken for the request to complete, in milliseconds. - * @property {Object} payload - The response payload from the hardware status request. - * @property {boolean} status - The status of the request (true if successful, false otherwise). - * @property {number} code - The response code (200 if successful, error code otherwise). - * @property {string} message - The message indicating the result of the hardware status request. - */ - async requestHardware(job) { - try { - return await this.requestHttp(job); - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestHardware"; - throw error; - } - } - - /** - * Sends a request to inspect a Docker container and returns its status. - * - * @param {Object} job - The job object containing the data for the Docker request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.url - The container ID or name to inspect. - * @param {string} job.data._id - The monitor ID for the Docker request. - * @param {string} job.data.type - The type of request, which is "docker". - * @returns {Promise} An object containing the Docker container status details. - * @property {string} monitorId - The monitor ID for the Docker request. - * @property {string} type - The type of request ("docker"). - * @property {number} responseTime - The time taken for the Docker inspection to complete, in milliseconds. - * @property {boolean} status - The status of the container (true if running, false otherwise). - * @property {number} code - The response code (200 if successful, error code otherwise). - * @property {string} message - The message indicating the result of the Docker inspection. - */ - async requestDocker(job) { - try { - const docker = new this.Docker({ - socketPath: "/var/run/docker.sock", - handleError: true, // Enable error handling - }); - - const containers = await docker.listContainers({ all: true }); - const containerExists = containers.some((c) => c.Id.startsWith(job.data.url)); - if (!containerExists) { - throw new Error(this.stringService.dockerNotFound); - } - const container = docker.getContainer(job.data.url); - - const { response, responseTime, error } = await this.timeRequest(() => - container.inspect() - ); - - const dockerResponse = { - monitorId: job.data._id, - type: job.data.type, - responseTime, - }; - - if (error) { - dockerResponse.status = false; - dockerResponse.code = error.statusCode || this.NETWORK_ERROR; - dockerResponse.message = - error.reason || "Failed to fetch Docker container information"; - return dockerResponse; - } - dockerResponse.status = response?.State?.Status === "running" ? true : false; - dockerResponse.code = 200; - dockerResponse.message = "Docker container status fetched successfully"; - return dockerResponse; - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestDocker"; - throw error; - } - } - - async requestPort(job) { - try { - const { url, port } = job.data; - const { response, responseTime, error } = await this.timeRequest(async () => { - return new Promise((resolve, reject) => { - const socket = this.net.createConnection( - { - host: url, - port, - }, - () => { - socket.end(); - socket.destroy(); - resolve({ success: true }); - } - ); - - socket.setTimeout(5000); - socket.on("timeout", () => { - socket.destroy(); - reject(new Error("Connection timeout")); - }); - - socket.on("error", (err) => { - socket.destroy(); - reject(err); - }); - }); - }); - - const portResponse = { - monitorId: job.data._id, - type: job.data.type, - responseTime, - }; - - if (error) { - portResponse.status = false; - portResponse.code = this.NETWORK_ERROR; - portResponse.message = this.stringService.portFail; - return portResponse; - } - - portResponse.status = response.success; - portResponse.code = 200; - portResponse.message = this.stringService.portSuccess; - return portResponse; - } catch (error) { - error.service = this.SERVICE_NAME; - error.method = "requestTCP"; - throw error; - } - } - - async requestDistributedHttp(job) { - try { - const monitor = job.data; - const CALLBACK_URL = process.env.CALLBACK_URL; - - const response = await this.axios.post( - UPROCK_ENDPOINT, - { - id: monitor._id, - url: monitor.url, - callback: `${CALLBACK_URL}/api/v1/distributed-uptime/callback`, - }, - { - headers: { - "Content-Type": "application/json", - "x-checkmate-key": process.env.UPROCK_API_KEY, - }, - } - ); - if (response.data.success === false) { - throw new Error(response.data.message); - } - } catch (error) { - console.log(error.message); - error.service = this.SERVICE_NAME; - error.method = "requestDistributedHttp"; - throw error; - } - } - - /** - * Handles unsupported job types by throwing an error with details. - * - * @param {string} type - The unsupported job type that was provided - * @throws {Error} An error with service name, method name and unsupported type message - */ - handleUnsupportedType(type) { - const err = new Error(`Unsupported type: ${type}`); - err.service = this.SERVICE_NAME; - err.method = "getStatus"; - throw err; - } - - async requestWebhook(platform, url, message) { - try { - const response = await this.axios.post(url, message, { - headers: { - 'Content-Type': 'application/json' - } - }); - - return { - type: 'webhook', - status: true, - code: response.status, - message: `Successfully sent ${platform} notification`, - payload: response.data - }; - - } catch (error) { - this.logger.warn({ - message: error.message, - service: this.SERVICE_NAME, - method: 'requestWebhook', - url, - platform, - error: error.message, - statusCode: error.response?.status, - responseData: error.response?.data, - requestPayload: message - }); - - return { - type: 'webhook', - status: false, - code: error.response?.status || this.NETWORK_ERROR, - message: `Failed to send ${platform} notification`, - payload: error.response?.data - }; - } - } - - - /** - * Gets the status of a job based on its type and returns the appropriate response. - * - * @param {Object} job - The job object containing the data for the status request. - * @param {Object} job.data - The data object within the job. - * @param {string} job.data.type - The type of the job (e.g., "ping", "http", "pagespeed", "hardware"). - * @returns {Promise} The response object from the appropriate request method. - * @throws {Error} Throws an error if the job type is unsupported. - */ - async getStatus(job) { - const type = job?.data?.type ?? "unknown"; - switch (type) { - case this.TYPE_PING: - return await this.requestPing(job); - case this.TYPE_HTTP: - return await this.requestHttp(job); - case this.TYPE_PAGESPEED: - return await this.requestPagespeed(job); - case this.TYPE_HARDWARE: - return await this.requestHardware(job); - case this.TYPE_DOCKER: - return await this.requestDocker(job); - case this.TYPE_PORT: - return await this.requestPort(job); - case this.TYPE_DISTRIBUTED_HTTP: - return await this.requestDistributedHttp(job); - - default: - return this.handleUnsupportedType(type); - } - } -} - -export default NetworkService; diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js deleted file mode 100644 index 2546df3f9..000000000 --- a/Server/service/notificationService.js +++ /dev/null @@ -1,298 +0,0 @@ -const SERVICE_NAME = "NotificationService"; -const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot"; -const PLATFORM_TYPES = ['telegram', 'slack', 'discord']; - -const MESSAGE_FORMATTERS = { - telegram: (messageText, chatId) => ({ chat_id: chatId, text: messageText }), - slack: (messageText) => ({ text: messageText }), - discord: (messageText) => ({ content: messageText }) -}; - -class NotificationService { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of NotificationService. - * - * @param {Object} emailService - The email service used for sending notifications. - * @param {Object} db - The database instance for storing notification data. - * @param {Object} logger - The logger instance for logging activities. - * @param {Object} networkService - The network service for sending webhook notifications. - */ - constructor(emailService, db, logger, networkService, stringService) { - this.SERVICE_NAME = SERVICE_NAME; - this.emailService = emailService; - this.db = db; - this.logger = logger; - this.networkService = networkService; - this.stringService = stringService; - } - - /** - * Formats a notification message based on the monitor status and platform. - * - * @param {Object} monitor - The monitor object. - * @param {string} monitor.name - The name of the monitor. - * @param {string} monitor.url - The URL of the monitor. - * @param {boolean} status - The current status of the monitor (true for up, false for down). - * @param {string} platform - The notification platform (e.g., "telegram", "slack", "discord"). - * @param {string} [chatId] - The chat ID for platforms that require it (e.g., Telegram). - * @returns {Object|null} The formatted message object for the specified platform, or null if the platform is unsupported. - */ - - formatNotificationMessage(monitor, status, platform, chatId) { - const messageText = this.stringService.getMonitorStatus( - monitor.name, - status, - monitor.url - ); - - if (!PLATFORM_TYPES.includes(platform)) { - return undefined; - } - - return MESSAGE_FORMATTERS[platform](messageText, chatId); - } - - /** - * Sends a webhook notification to a specified platform. - * - * @param {Object} networkResponse - The response object from the network. - * @param {Object} networkResponse.monitor - The monitor object. - * @param {boolean} networkResponse.status - The monitor's status (true for up, false for down). - * @param {Object} notification - The notification settings. - * @param {string} notification.platform - The target platform ("telegram", "slack", "discord"). - * @param {Object} notification.config - The configuration object for the webhook. - * @param {string} notification.config.webhookUrl - The webhook URL for the platform. - * @param {string} [notification.config.botToken] - The bot token for Telegram notifications. - * @param {string} [notification.config.chatId] - The chat ID for Telegram notifications. - * @returns {Promise} A promise that resolves to true if the notification was sent successfully, otherwise false. - */ - - async sendWebhookNotification(networkResponse, notification) { - const { monitor, status } = networkResponse; - const { platform } = notification; - const { webhookUrl, botToken, chatId } = notification.config; - - // Early return if platform is not supported - if (!PLATFORM_TYPES.includes(platform)) { - this.logger.warn({ - message: this.stringService.getWebhookUnsupportedPlatform(platform), - service: this.SERVICE_NAME, - method: 'sendWebhookNotification', - platform - }); - return false; - } - - // Early return for telegram if required fields are missing - if (platform === 'telegram' && (!botToken || !chatId)) { - this.logger.warn({ - message: 'Missing required fields for Telegram notification', - service: this.SERVICE_NAME, - method: 'sendWebhookNotification', - platform - }); - return false; - } - - let url = webhookUrl; - if (platform === 'telegram') { - url = `${TELEGRAM_API_BASE_URL}${botToken}/sendMessage`; - } - - // Now that we know the platform is valid, format the message - const message = this.formatNotificationMessage(monitor, status, platform, chatId); - - try { - const response = await this.networkService.requestWebhook(platform, url, message); - return response.status; - } catch (error) { - this.logger.error({ - message: this.stringService.getWebhookSendError(platform), - service: this.SERVICE_NAME, - method: 'sendWebhookNotification', - error: error.message, - stack: error.stack, - url, - platform, - requestPayload: message - }); - return false; - } - } - - /** - * Sends an email notification for hardware infrastructure alerts - * - * @async - * @function sendHardwareEmail - * @param {Object} networkResponse - Response object containing monitor information - * @param {string} address - Email address to send the notification to - * @param {Array} [alerts=[]] - List of hardware alerts to include in the email - * @returns {Promise} - Indicates whether email was sent successfully - * @throws {Error} - */ - async sendHardwareEmail(networkResponse, address, alerts = []) { - if (alerts.length === 0) return false; - const { monitor, status, prevStatus } = networkResponse; - const template = "hardwareIncidentTemplate"; - const context = { monitor: monitor.name, url: monitor.url, alerts }; - const subject = `Monitor ${monitor.name} infrastructure alerts`; - this.emailService.buildAndSendEmail(template, context, address, subject); - return true; - } - - /** - * Sends an email notification about monitor status change - * - * @async - * @function sendEmail - * @param {Object} networkResponse - Response object containing monitor status information - * @param {string} address - Email address to send the notification to - * @returns {Promise} - Indicates email was sent successfully - */ - async sendEmail(networkResponse, address) { - const { monitor, status, prevStatus } = networkResponse; - const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate"; - const context = { monitor: monitor.name, url: monitor.url }; - const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`; - this.emailService.buildAndSendEmail(template, context, address, subject); - return true; - } - - async handleStatusNotifications(networkResponse) { - try { - // If status hasn't changed, we're done - if (networkResponse.statusChanged === false) return false; - // if prevStatus is undefined, monitor is resuming, we're done - if (networkResponse.prevStatus === undefined) return false; - - const notifications = await this.db.getNotificationsByMonitorId(networkResponse.monitorId); - - for (const notification of notifications) { - if (notification.type === "email") { - await this.sendEmail(networkResponse, notification.address); - } else if (notification.type === "webhook") { - await this.sendWebhookNotification(networkResponse, notification); - } - // Handle other types of notifications here - } - return true; - } catch (error) { - this.logger.warn({ - message: error.message, - service: this.SERVICE_NAME, - method: "handleNotifications", - stack: error.stack, - }); - } - } - /** - * Handles status change notifications for a monitor - * - * @async - * @function handleStatusNotifications - * @param {Object} networkResponse - Response object containing monitor status information - * @returns {Promise} - Indicates whether notifications were processed - * @throws {Error} - */ - async handleHardwareNotifications(networkResponse) { - const thresholds = networkResponse?.monitor?.thresholds; - if (thresholds === undefined) return false; // No thresholds set, we're done - - // Get thresholds from monitor - const { - usage_cpu: cpuThreshold = -1, - usage_memory: memoryThreshold = -1, - usage_disk: diskThreshold = -1, - } = thresholds; - - // Get metrics from response - const metrics = networkResponse?.payload?.data ?? null; - if (metrics === null) return false; - - const { - cpu: { usage_percent: cpuUsage = -1 } = {}, - memory: { usage_percent: memoryUsage = -1 } = {}, - disk = [], - } = metrics; - - const alerts = { - cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false, - memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false, - disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold) - ? true - : false, - }; - - const notifications = await this.db.getNotificationsByMonitorId( - networkResponse.monitorId - ); - for (const notification of notifications) { - const alertsToSend = []; - const alertTypes = ["cpu", "memory", "disk"]; - - for (const type of alertTypes) { - // Iterate over each alert type to see if any need to be decremented - if (alerts[type] === true) { - notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered - - if (notification[`${type}AlertThreshold`] <= 0) { - // If threshold drops below 0, reset and send notification - notification[`${type}AlertThreshold`] = notification.alertThreshold; - - const formatAlert = { - cpu: () => - `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, - memory: () => - `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, - disk: () => - `Your current disk usage: ${disk - .map((d, idx) => `(Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%)`) - .join( - ", " - )} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, - }; - alertsToSend.push(formatAlert[type]()); - } - } - } - - await notification.save(); - - if (alertsToSend.length === 0) continue; // No alerts to send, we're done - - if (notification.type === "email") { - this.sendHardwareEmail(networkResponse, notification.address, alertsToSend); - } - } - return true; - } - - /** - * Handles notifications for different monitor types - * - * @async - * @function handleNotifications - * @param {Object} networkResponse - Response object containing monitor information - * @returns {Promise} - Indicates whether notifications were processed successfully - */ - async handleNotifications(networkResponse) { - try { - if (networkResponse.monitor.type === "hardware") { - this.handleHardwareNotifications(networkResponse); - } - this.handleStatusNotifications(networkResponse); - return true; - } catch (error) { - this.logger.warn({ - message: error.message, - service: this.SERVICE_NAME, - method: "handleNotifications", - stack: error.stack, - }); - } - } -} - -export default NotificationService; diff --git a/Server/service/serviceRegistry.js b/Server/service/serviceRegistry.js deleted file mode 100644 index 69a3b8be5..000000000 --- a/Server/service/serviceRegistry.js +++ /dev/null @@ -1,35 +0,0 @@ -const SERVICE_NAME = "ServiceRegistry"; -import logger from "../utils/logger.js"; -class ServiceRegistry { - static SERVICE_NAME = SERVICE_NAME; - constructor() { - this.services = {}; - } - - register(name, service) { - logger.info({ - message: `Registering service ${name}`, - service: SERVICE_NAME, - method: "register", - }); - this.services[name] = service; - } - - get(name) { - if (!this.services[name]) { - logger.error({ - message: `Service ${name} is not registered`, - service: SERVICE_NAME, - method: "get", - }); - throw new Error(`Service ${name} is not registered`); - } - return this.services[name]; - } - - listServices() { - return Object.keys(this.services); - } -} - -export default new ServiceRegistry(); diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js deleted file mode 100644 index 24931f6ce..000000000 --- a/Server/service/settingsService.js +++ /dev/null @@ -1,84 +0,0 @@ -const SERVICE_NAME = "SettingsService"; -import dotenv from "dotenv"; -dotenv.config(); -const envConfig = { - logLevel: process.env.LOG_LEVEL, - apiBaseUrl: undefined, - clientHost: process.env.CLIENT_HOST, - jwtSecret: process.env.JWT_SECRET, - refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET, - dbType: process.env.DB_TYPE, - dbConnectionString: process.env.DB_CONNECTION_STRING, - redisHost: process.env.REDIS_HOST, - redisPort: process.env.REDIS_PORT, - jwtTTL: process.env.TOKEN_TTL, - refreshTokenTTL: process.env.REFRESH_TOKEN_TTL, - pagespeedApiKey: process.env.PAGESPEED_API_KEY, - systemEmailHost: process.env.SYSTEM_EMAIL_HOST, - systemEmailPort: process.env.SYSTEM_EMAIL_PORT, - systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS, - systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD, -}; -/** - * SettingsService - * - * This service is responsible for loading and managing the application settings. - * It gives priority to environment variables and will only load settings - * from the database if they are not set in the environment. - */ -class SettingsService { - static SERVICE_NAME = SERVICE_NAME; - /** - * Constructs a new SettingsService - * @constructor - * @throws {Error} - */ constructor(appSettings) { - this.appSettings = appSettings; - this.settings = { ...envConfig }; - } - /** - * Load settings from the database and merge with environment settings. - * If there are any settings that weren't set by environment variables, use user settings from the database. - * @returns {Promise} The merged settings. - * @throws Will throw an error if settings are not found in the database or if settings have not been loaded. - */ async loadSettings() { - try { - const dbSettings = await this.appSettings.findOne(); - if (!this.settings) { - throw new Error("Settings not found"); - } - - // If there are any settings that weren't set by environment variables, use user settings from DB - for (const key in envConfig) { - if (envConfig[key] === undefined && dbSettings[key] !== undefined) { - this.settings[key] = dbSettings[key]; - } - } - return this.settings; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "loadSettings") : null; - throw error; - } - } - /** - * Reload settings by calling loadSettings. - * @returns {Promise} The reloaded settings. - */ - async reloadSettings() { - return this.loadSettings(); - } - /** - * Get the current settings. - * @returns {Object} The current settings. - * @throws Will throw an error if settings have not been loaded. - */ - getSettings() { - if (!this.settings) { - throw new Error("Settings have not been loaded"); - } - return this.settings; - } -} - -export default SettingsService; diff --git a/Server/service/statusService.js b/Server/service/statusService.js deleted file mode 100644 index fa263da75..000000000 --- a/Server/service/statusService.js +++ /dev/null @@ -1,200 +0,0 @@ -const SERVICE_NAME = "StatusService"; - -class StatusService { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of StatusService. - * - * @param {Object} db - The database instance. - * @param {Object} logger - The logger instance. - */ - constructor(db, logger) { - this.db = db; - this.logger = logger; - this.SERVICE_NAME = SERVICE_NAME; - } - - getStatusString = (status) => { - if (status === true) return "up"; - if (status === false) return "down"; - return "unknown"; - }; - /** - * Updates the status of a monitor based on the network response. - * - * @param {Object} networkResponse - The network response containing monitorId and status. - * @param {string} networkResponse.monitorId - The ID of the monitor. - * @param {string} networkResponse.status - The new status of the monitor. - * @returns {Promise} - A promise that resolves to an object containinfg the monitor, statusChanged flag, and previous status if the status changed, or false if an error occurred. - * @returns {Promise} returnObject - The object returned by the function. - * @returns {Object} returnObject.monitor - The monitor object. - * @returns {boolean} returnObject.statusChanged - Flag indicating if the status has changed. - * @returns {boolean} returnObject.prevStatus - The previous status of the monitor - */ - updateStatus = async (networkResponse) => { - this.insertCheck(networkResponse); - try { - const { monitorId, status } = networkResponse; - const monitor = await this.db.getMonitorById(monitorId); - // No change in monitor status, return early - if (monitor.status === status) - return { monitor, statusChanged: false, prevStatus: monitor.status }; - // Monitor status changed, save prev status and update monitor - - this.logger.info({ - service: this.SERVICE_NAME, - message: `${monitor.name} went from ${this.getStatusString(monitor.status)} to ${this.getStatusString(status)}`, - prevStatus: monitor.status, - newStatus: status, - }); - - const prevStatus = monitor.status; - monitor.status = status; - await monitor.save(); - - return { - monitor, - statusChanged: true, - prevStatus: prevStatus, - }; - // - } catch (error) { - this.logger.error({ - service: this.SERVICE_NAME, - message: error.message, - method: "updateStatus", - stack: error.stack, - }); - throw error; - } - }; - - /** - * Builds a check object from the network response. - * - * @param {Object} networkResponse - The network response object. - * @param {string} networkResponse.monitorId - The monitor ID. - * @param {string} networkResponse.type - The type of the response. - * @param {string} networkResponse.status - The status of the response. - * @param {number} networkResponse.responseTime - The response time. - * @param {number} networkResponse.code - The status code. - * @param {string} networkResponse.message - The message. - * @param {Object} networkResponse.payload - The payload of the response. - * @returns {Object} The check object. - */ - buildCheck = (networkResponse) => { - const { - monitorId, - teamId, - type, - status, - responseTime, - code, - message, - payload, - first_byte_took, - body_read_took, - dns_took, - conn_took, - connect_took, - tls_took, - } = networkResponse; - - const check = { - monitorId, - teamId, - status, - statusCode: code, - responseTime, - message, - first_byte_took, - body_read_took, - dns_took, - conn_took, - connect_took, - tls_took, - }; - - if (type === "distributed_http") { - check.continent = payload.continent; - check.countryCode = payload.country_code; - check.city = payload.city; - check.location = payload.location; - check.uptBurnt = payload.upt_burnt; - check.first_byte_took = payload.first_byte_took; - check.body_read_took = payload.body_read_took; - check.dns_took = payload.dns_took; - check.conn_took = payload.conn_took; - check.connect_took = payload.connect_took; - check.tls_took = payload.tls_took; - } - - if (type === "pagespeed") { - const categories = payload.lighthouseResult?.categories; - const audits = payload.lighthouseResult?.audits; - const { - "cumulative-layout-shift": cls = 0, - "speed-index": si = 0, - "first-contentful-paint": fcp = 0, - "largest-contentful-paint": lcp = 0, - "total-blocking-time": tbt = 0, - } = audits; - check.accessibility = (categories.accessibility?.score || 0) * 100; - check.bestPractices = (categories["best-practices"]?.score || 0) * 100; - check.seo = (categories.seo?.score || 0) * 100; - check.performance = (categories.performance?.score || 0) * 100; - check.audits = { cls, si, fcp, lcp, tbt }; - } - - if (type === "hardware") { - const { cpu, memory, disk, host } = payload?.data ?? {}; - const { errors } = payload?.errors ?? []; - check.cpu = cpu ?? {}; - check.memory = memory ?? {}; - check.disk = disk ?? {}; - check.host = host ?? {}; - check.errors = errors ?? []; - } - return check; - }; - - /** - * Inserts a check into the database based on the network response. - * - * @param {Object} networkResponse - The network response object. - * @param {string} networkResponse.monitorId - The monitor ID. - * @param {string} networkResponse.type - The type of the response. - * @param {string} networkResponse.status - The status of the response. - * @param {number} networkResponse.responseTime - The response time. - * @param {number} networkResponse.code - The status code. - * @param {string} networkResponse.message - The message. - * @param {Object} networkResponse.payload - The payload of the response. - * @returns {Promise} A promise that resolves when the check is inserted. - */ - insertCheck = async (networkResponse) => { - try { - const operationMap = { - http: this.db.createCheck, - ping: this.db.createCheck, - pagespeed: this.db.createPageSpeedCheck, - hardware: this.db.createHardwareCheck, - docker: this.db.createCheck, - port: this.db.createCheck, - distributed_http: this.db.createDistributedCheck, - }; - const operation = operationMap[networkResponse.type]; - - const check = this.buildCheck(networkResponse); - await operation(check); - } catch (error) { - this.logger.error({ - message: error.message, - service: this.SERVICE_NAME, - method: "insertCheck", - details: `Error inserting check for monitor: ${networkResponse?.monitorId}`, - stack: error.stack, - }); - } - }; -} -export default StatusService; diff --git a/Server/service/stringService.js b/Server/service/stringService.js deleted file mode 100644 index e3ab03ca5..000000000 --- a/Server/service/stringService.js +++ /dev/null @@ -1,388 +0,0 @@ -class StringService { - static SERVICE_NAME = "StringService"; - - constructor(translationService) { - if (StringService.instance) { - return StringService.instance; - } - - this.translationService = translationService; - this._language = 'en'; // default language - StringService.instance = this; - } - - setLanguage(language) { - this._language = language; - } - - get language() { - return this._language; - } - - // Auth Messages - get dontHaveAccount() { - return this.translationService.getTranslation('dontHaveAccount'); - } - - get email() { - return this.translationService.getTranslation('email'); - } - - get forgotPassword() { - return this.translationService.getTranslation('forgotPassword'); - } - - get password() { - return this.translationService.getTranslation('password'); - } - - get signUp() { - return this.translationService.getTranslation('signUp'); - } - - get submit() { - return this.translationService.getTranslation('submit'); - } - - get title() { - return this.translationService.getTranslation('title'); - } - - get continue() { - return this.translationService.getTranslation('continue'); - } - - get enterEmail() { - return this.translationService.getTranslation('enterEmail'); - } - - get authLoginTitle() { - return this.translationService.getTranslation('authLoginTitle'); - } - - get authLoginEnterPassword() { - return this.translationService.getTranslation('authLoginEnterPassword'); - } - - get commonPassword() { - return this.translationService.getTranslation('commonPassword'); - } - - get commonBack() { - return this.translationService.getTranslation('commonBack'); - } - - get authForgotPasswordTitle() { - return this.translationService.getTranslation('authForgotPasswordTitle'); - } - - get authForgotPasswordResetPassword() { - return this.translationService.getTranslation('authForgotPasswordResetPassword'); - } - - get createPassword() { - return this.translationService.getTranslation('createPassword'); - } - - get createAPassword() { - return this.translationService.getTranslation('createAPassword'); - } - - get authRegisterAlreadyHaveAccount() { - return this.translationService.getTranslation('authRegisterAlreadyHaveAccount'); - } - - get commonAppName() { - return this.translationService.getTranslation('commonAppName'); - } - - get authLoginEnterEmail() { - return this.translationService.getTranslation('authLoginEnterEmail'); - } - - get authRegisterTitle() { - return this.translationService.getTranslation('authRegisterTitle'); - } - - get monitorGetAll() { - return this.translationService.getTranslation('monitorGetAll'); - } - - get monitorGetById() { - return this.translationService.getTranslation('monitorGetById'); - } - - get monitorCreate() { - return this.translationService.getTranslation('monitorCreate'); - } - - get monitorEdit() { - return this.translationService.getTranslation('monitorEdit'); - } - - get monitorDelete() { - return this.translationService.getTranslation('monitorDelete'); - } - - get monitorPause() { - return this.translationService.getTranslation('monitorPause'); - } - - get monitorResume() { - return this.translationService.getTranslation('monitorResume'); - } - - get monitorDemoAdded() { - return this.translationService.getTranslation('monitorDemoAdded'); - } - - get monitorStatsById() { - return this.translationService.getTranslation('monitorStatsById'); - } - - get monitorCertificate() { - return this.translationService.getTranslation('monitorCertificate'); - } - - // Maintenance Window Messages - get maintenanceWindowCreate() { - return this.translationService.getTranslation('maintenanceWindowCreate'); - } - - get maintenanceWindowGetById() { - return this.translationService.getTranslation('maintenanceWindowGetById'); - } - - get maintenanceWindowGetByTeam() { - return this.translationService.getTranslation('maintenanceWindowGetByTeam'); - } - - get maintenanceWindowDelete() { - return this.translationService.getTranslation('maintenanceWindowDelete'); - } - - get maintenanceWindowEdit() { - return this.translationService.getTranslation('maintenanceWindowEdit'); - } - - // Webhook Messages - get webhookUnsupportedPlatform() { - return this.translationService.getTranslation('webhookUnsupportedPlatform'); - } - - get webhookSendError() { - return this.translationService.getTranslation('webhookSendError'); - } - - get webhookSendSuccess() { - return this.translationService.getTranslation('webhookSendSuccess'); - } - - getWebhookUnsupportedPlatform(platform) { - return this.translationService.getTranslation('webhookUnsupportedPlatform') - .replace('{platform}', platform); - } - - getWebhookSendError(platform) { - return this.translationService.getTranslation('webhookSendError') - .replace('{platform}', platform); - } - - getMonitorStatus(name, status, url) { - return this.translationService.getTranslation('monitorStatus') - .replace('{name}', name) - .replace('{status}', status ? "up" : "down") - .replace('{url}', url); - } - - // Error Messages - get unknownError() { - return this.translationService.getTranslation('unknownError'); - } - - get friendlyError() { - return this.translationService.getTranslation('friendlyError'); - } - - get authIncorrectPassword() { - return this.translationService.getTranslation('authIncorrectPassword'); - } - - get unauthorized() { - return this.translationService.getTranslation('unauthorized'); - } - - get authAdminExists() { - return this.translationService.getTranslation('authAdminExists'); - } - - get authInviteNotFound() { - return this.translationService.getTranslation('authInviteNotFound'); - } - - get unknownService() { - return this.translationService.getTranslation('unknownService'); - } - - get noAuthToken() { - return this.translationService.getTranslation('noAuthToken'); - } - - get invalidAuthToken() { - return this.translationService.getTranslation('invalidAuthToken'); - } - - get expiredAuthToken() { - return this.translationService.getTranslation('expiredAuthToken'); - } - - // Queue Messages - get queueGetMetrics() { - return this.translationService.getTranslation('queueGetMetrics'); - } - - get queueAddJob() { - return this.translationService.getTranslation('queueAddJob'); - } - - get queueObliterate() { - return this.translationService.getTranslation('queueObliterate'); - } - - // Job Queue Messages - get jobQueueDeleteJobSuccess() { - return this.translationService.getTranslation('jobQueueDeleteJobSuccess'); - } - - get jobQueuePauseJob() { - return this.translationService.getTranslation('jobQueuePauseJob'); - } - - get jobQueueResumeJob() { - return this.translationService.getTranslation('jobQueueResumeJob'); - } - - // Status Page Messages - get statusPageByUrl() { - return this.translationService.getTranslation('statusPageByUrl'); - } - - get statusPageCreate() { - return this.translationService.getTranslation('statusPageCreate'); - } - - get statusPageDelete() { - return this.translationService.getTranslation('statusPageDelete'); - } - - get statusPageUpdate() { - return this.translationService.getTranslation('statusPageUpdate'); - } - - get statusPageNotFound() { - return this.translationService.getTranslation('statusPageNotFound'); - } - - get statusPageByTeamId() { - return this.translationService.getTranslation('statusPageByTeamId'); - } - - get statusPageUrlNotUnique() { - return this.translationService.getTranslation('statusPageUrlNotUnique'); - } - - // Docker Messages - get dockerFail() { - return this.translationService.getTranslation('dockerFail'); - } - - get dockerNotFound() { - return this.translationService.getTranslation('dockerNotFound'); - } - - get dockerSuccess() { - return this.translationService.getTranslation('dockerSuccess'); - } - - // Port Messages - get portFail() { - return this.translationService.getTranslation('portFail'); - } - - get portSuccess() { - return this.translationService.getTranslation('portSuccess'); - } - - // Alert Messages - get alertCreate() { - return this.translationService.getTranslation('alertCreate'); - } - - get alertGetByUser() { - return this.translationService.getTranslation('alertGetByUser'); - } - - get alertGetByMonitor() { - return this.translationService.getTranslation('alertGetByMonitor'); - } - - get alertGetById() { - return this.translationService.getTranslation('alertGetById'); - } - - get alertEdit() { - return this.translationService.getTranslation('alertEdit'); - } - - get alertDelete() { - return this.translationService.getTranslation('alertDelete'); - } - - getDeletedCount(count) { - return this.translationService.getTranslation('deletedCount') - .replace('{count}', count); - } - - get pingSuccess() { - return this.translationService.getTranslation('pingSuccess'); - } - - get getAppSettings() { - return this.translationService.getTranslation('getAppSettings'); - } - - get httpNetworkError() { - return this.translationService.getTranslation('httpNetworkError'); - } - - get httpNotJson() { - return this.translationService.getTranslation('httpNotJson'); - } - - get httpJsonPathError() { - return this.translationService.getTranslation('httpJsonPathError'); - } - - get httpEmptyResult() { - return this.translationService.getTranslation('httpEmptyResult'); - } - - get httpMatchSuccess() { - return this.translationService.getTranslation('httpMatchSuccess'); - } - - get httpMatchFail() { - return this.translationService.getTranslation('httpMatchFail'); - } - - get updateAppSettings() { - return this.translationService.getTranslation('updateAppSettings'); - } - - getDbFindMonitorById(monitorId) { - return this.translationService.getTranslation('dbFindMonitorById') - .replace('${monitorId}', monitorId); - } -} - -export default StringService; \ No newline at end of file diff --git a/Server/service/translationService.js b/Server/service/translationService.js deleted file mode 100644 index 64c62bff2..000000000 --- a/Server/service/translationService.js +++ /dev/null @@ -1,90 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -class TranslationService { - static SERVICE_NAME = 'TranslationService'; - - constructor(logger) { - this.logger = logger; - this.translations = {}; - this._language = 'en'; - this.localesDir = path.join(process.cwd(), 'locales'); - } - - setLanguage(language) { - this._language = language; - } - - get language() { - return this._language; - } - - async initialize() { - try { - await this.loadFromFiles(); - - } catch (error) { - this.logger.error({ - message: error.message, - service: 'TranslationService', - method: 'initialize', - stack: error.stack - }); - } - } - - async loadFromFiles() { - try { - if (!fs.existsSync(this.localesDir)) { - return false; - } - - const files = fs.readdirSync(this.localesDir).filter(file => file.endsWith('.json')); - - if (files.length === 0) { - return false; - } - - for (const file of files) { - const language = file.replace('.json', ''); - const filePath = path.join(this.localesDir, file); - const content = fs.readFileSync(filePath, 'utf8'); - this.translations[language] = JSON.parse(content); - } - - this.logger.info({ - message: 'Translations loaded from files successfully', - service: 'TranslationService', - method: 'loadFromFiles' - }); - - return true; - } catch (error) { - this.logger.error({ - message: error.message, - service: 'TranslationService', - method: 'loadFromFiles', - stack: error.stack - }); - return false; - } - } - - getTranslation(key) { - let language = this._language; - - try { - return this.translations[language]?.[key] || this.translations['en']?.[key] || key; - } catch (error) { - this.logger.error({ - message: error.message, - service: 'TranslationService', - method: 'getTranslation', - stack: error.stack - }); - return key; - } - } -} - -export default TranslationService; \ No newline at end of file diff --git a/Server/templates/addReview.mjml b/Server/templates/addReview.mjml deleted file mode 100644 index 22adbe8b0..000000000 --- a/Server/templates/addReview.mjml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - Message from Checkmate Service - - - - - - -

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 part of our community. -

-

-If you’re happy with Checkmate, we’d love to hear about your experience! Leaving a review on G2 helps others discover Checkmate and supports our ongoing improvements. -

-G2 Link: TBD -

-Thank you for taking the time to share your thoughts - we greatly appreciate it! -

-Checkmate Team -
-
- - - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/templates/employeeActivation.mjml b/Server/templates/employeeActivation.mjml deleted file mode 100644 index 34d5ea7c4..000000000 --- a/Server/templates/employeeActivation.mjml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - Message from Checkmate Service - - - - - -

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}}

-

Thank you.

-
-
- - - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/templates/hardwareIncident.mjml b/Server/templates/hardwareIncident.mjml deleted file mode 100644 index e27933fb9..000000000 --- a/Server/templates/hardwareIncident.mjml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - Message from BlueWave Infrastructure Monitoring - - - - - Infrastructure Alerts - - - - - - - -

Hello {{name}}!

-

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

- {{#each alerts}} -

• {{this}}

- {{/each}} -
-
- - - View Infrastructure Details - -

This email was sent by BlueWave Infrastructure Monitoring.

-
-
-
-
-
\ No newline at end of file diff --git a/Server/templates/noIncidentsThisWeek.mjml b/Server/templates/noIncidentsThisWeek.mjml deleted file mode 100644 index d31d63d10..000000000 --- a/Server/templates/noIncidentsThisWeek.mjml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - Message from Checkmate Service - - - No incidents this week! - - - - - - -

Hello {{name}}!

-

There were no incidents this week. Good job!

-

Current monitors:

-

Google: 100% availability

-

Canada.ca:100% availability

-
-
- - - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/templates/passwordReset.mjml b/Server/templates/passwordReset.mjml deleted file mode 100644 index e0b85a4f4..000000000 --- a/Server/templates/passwordReset.mjml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - Message from Checkmate Service - - - - - - -

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. -

- Reset Password -

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

- -

Thank you.

-
-
- - - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/templates/serverIsDown.mjml b/Server/templates/serverIsDown.mjml deleted file mode 100644 index 16df7a0a1..000000000 --- a/Server/templates/serverIsDown.mjml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - Message from Checkmate Service - - - - - {{monitor}} is down - - - - - - - -

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}}

-
-
- - - View incident details - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/templates/serverIsUp.mjml b/Server/templates/serverIsUp.mjml deleted file mode 100644 index 8dbaaf011..000000000 --- a/Server/templates/serverIsUp.mjml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - Message from Checkmate Service - - - - - {{monitor}} is up - - - - - - - -

Hello {{name}}!

-

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

-

Monitor name: {{monitor}}

-

URL: {{url}}

-
-
- - - View incident details - -

This email was sent by Checkmate.

-
-
-
-
-
\ No newline at end of file diff --git a/Server/templates/welcomeEmail.mjml b/Server/templates/welcomeEmail.mjml deleted file mode 100644 index af1bfc190..000000000 --- a/Server/templates/welcomeEmail.mjml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - Message from Checkmate Service - - - - - - -

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. -

-

- Checkmate is an automated way of checking whether a service such as a - website or an application is available or not. -

-

We hope you find our service as valuable as we do.

-

Thank you.

-
-
- - - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/Server/tests/controllers/authController.test.js b/Server/tests/controllers/authController.test.js deleted file mode 100644 index 3f45d1c42..000000000 --- a/Server/tests/controllers/authController.test.js +++ /dev/null @@ -1,975 +0,0 @@ -import { - issueToken, - registerUser, - loginUser, - refreshAuthToken, - editUser, - checkSuperadminExists, - requestRecovery, - validateRecovery, - resetPassword, - deleteUser, - getAllUsers, -} from "../../controllers/authController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -import { getTokenFromHeaders, tokenType } from "../../utils/utils.js"; -import logger from "../../utils/logger.js"; -import e from "cors"; - -describe("Auth Controller - issueToken", function() { - let stub; - - afterEach(function() { - sinon.restore(); // Restore stubs after each test - }); - - it("should reject with an error if jwt.sign fails", function() { - const error = new Error("jwt.sign error"); - stub = sinon.stub(jwt, "sign").throws(error); - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - expect(() => issueToken(payload, tokenType.ACCESS_TOKEN, appSettings)).to.throw( - error - ); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function() { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", function() { - const payload = { id: "123" }; - const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" }; - const expectedToken = "mockToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.ACCESS_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", function() { - const payload = {}; - const appSettings = { refreshTokenSecret: "my_refresh_secret" }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); - - it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", function() { - const payload = {}; - const appSettings = { - refreshTokenSecret: "my_refresh_secret", - refreshTokenTTL: "7d", - }; - const expectedToken = "mockRefreshToken"; - - stub = sinon.stub(jwt, "sign").returns(expectedToken); - const token = issueToken(payload, tokenType.REFRESH_TOKEN, appSettings); - expect(token).to.equal(expectedToken); - }); -}); - -describe("Auth Controller - registerUser", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: { - firstName: "firstname", - lastName: "lastname", - email: "test@test.com", - password: "Uptime1!", - role: ["admin"], - teamId: "123", - inviteToken: "invite", - }, - db: { - checkSuperadmin: sinon.stub(), - getInviteTokenAndDelete: sinon.stub(), - updateAppSettings: sinon.stub(), - insertUser: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - file: {}, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = {}; - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if checkSuperadmin fails", async function() { - req.db.checkSuperadmin.throws(new Error("checkSuperadmin error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should reject with an error if getInviteTokenAndDelete fails", async function() { - req.db.checkSuperadmin.returns(true); - req.db.getInviteTokenAndDelete.throws(new Error("getInviteTokenAndDelete error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getInviteTokenAndDelete error"); - }); - - it("should reject with an error if updateAppSettings fails", async function() { - req.db.checkSuperadmin.returns(false); - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if insertUser fails", async function() { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.rejects(new Error("insertUser error")); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("insertUser error"); - }); - - it("should reject with an error if settingsService.getSettings fails", async function() { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.resolves({ _id: "123" }); - req.settingsService.getSettings.rejects( - new Error("settingsService.getSettings error") - ); - await registerUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should log an error if emailService.buildAndSendEmail fails", async function() { - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns({ _id: "123" }); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.rejects(new Error("emailService error")); - await registerUser(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("emailService error"); - }); - - it("should return a success message and data if all operations are successful", async function() { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(false); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return a success message and data if all operations are successful and superAdmin true", async function() { - const user = { _id: "123" }; - req.db.checkSuperadmin.resolves(true); - req.db.updateAppSettings.resolves(); - req.db.insertUser.returns(user); - req.settingsService.getSettings.returns({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_secret", - }); - req.emailService.buildAndSendEmail.resolves("message-id"); - await registerUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_CREATE_USER, - data: { user, token: sinon.match.string, refreshToken: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - loginUser", function() { - let req, res, next, user; - - beforeEach(function() { - req = { - body: { email: "test@example.com", password: "Password123!" }, - db: { - getUserByEmail: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_token", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - user = { - _doc: { - email: "test@example.com", - }, - comparePassword: sinon.stub(), - }; - }); - - it("should reject with an error if validation fails", async function() { - req.body = {}; - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function() { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should login user successfully", async function() { - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(true); - await loginUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_LOGIN_USER, - data: { - user: { - email: "test@example.com", - avatarImage: undefined, - }, - token: sinon.match.string, - refreshToken: sinon.match.string, - }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject a user with an incorrect password", async function() { - req.body = { - email: "test@test.com", - password: "Password123!", - }; - req.db.getUserByEmail.resolves(user); - user.comparePassword.resolves(false); - await loginUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal( - errorMessages.AUTH_INCORRECT_PASSWORD - ); - }); -}); - -describe("Auth Controller - refreshAuthToken", function() { - let req, res, next, issueTokenStub; - - beforeEach(function() { - req = { - headers: { - "x-refresh-token": "valid_refresh_token", - authorization: "Bearer old_auth_token", - }, - settingsService: { - getSettings: sinon.stub().resolves({ - jwtSecret: "my_secret", - refreshTokenSecret: "my_refresh_secret", - }), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(jwt, "verify"); - - issueTokenStub = sinon.stub().returns("new_auth_token"); - sinon.replace({ issueToken }, "issueToken", issueTokenStub); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject if no refresh token is provided", async function() { - delete req.headers["x-refresh-token"]; - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.NO_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is invalid", async function() { - jwt.verify.yields(new Error("invalid token")); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.INVALID_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if the refresh token is expired", async function() { - const error = new Error("Token expired"); - error.name = "TokenExpiredError"; - jwt.verify.yields(error); - await refreshAuthToken(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal(errorMessages.EXPIRED_REFRESH_TOKEN); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject if settingsService.getSettings fails", async function() { - req.settingsService.getSettings.rejects( - new Error("settingsService.getSettings error") - ); - await refreshAuthToken(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error"); - }); - - it("should generate a new auth token if the refresh token is valid", async function() { - const decodedPayload = { expiresIn: "60" }; - jwt.verify.callsFake(() => { - return decodedPayload; - }); - await refreshAuthToken(req, res, next); - - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_TOKEN_REFRESHED, - data: { - user: decodedPayload, - token: sinon.match.string, - refreshToken: "valid_refresh_token", - }, - }) - ).to.be.true; - }); -}); - -describe("Auth Controller - editUser", function() { - let req, res, next, stub, user; - - beforeEach(function() { - req = { - params: { userId: "123" }, - body: { password: "Password1!", newPassword: "Password2!" }, - headers: { authorization: "Bearer token" }, - user: { _id: "123" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - getUserByEmail: sinon.stub(), - updateUser: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" }); - }); - - afterEach(function() { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = { invalid: 1 }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if param.userId !== req.user._id", async function() { - req.params = { userId: "456" }; - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - }); - - it("should reject with an error if !req.body.password and getUserByEmail fails", async function() { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should reject with an error if user.comparePassword fails", async function() { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().rejects(new Error("Bad Password Match")), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Bad Password Match"); - }); - - it("should reject with an error if user.comparePassword returns false", async function() { - req.db.getUserByEmail.returns({ - comparePassword: sinon.stub().returns(false), - }); - await editUser(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(401); - expect(next.firstCall.args[0].message).to.equal( - errorMessages.AUTH_INCORRECT_PASSWORD - ); - }); - - it("should edit a user if it receives a proper request", async function() { - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnce).to.be.true; - expect(req.db.updateUser.calledOnce).to.be.true; - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should edit a user if it receives a proper request and both password fields are undefined", async function() { - req.body.password = undefined; - req.body.newPassword = undefined; - req.db.getUserByEmail.resolves(user); - req.db.updateUser.resolves({ email: "test@example.com" }); - - await editUser(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_UPDATE_USER, - data: { email: "test@example.com" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should reject an edit request if password format is incorrect", async function() { - req.body = { password: "bad_password", newPassword: "bad_password" }; - const user = { - comparePassword: sinon.stub().resolves(true), - }; - req.db.getUserByEmail.resolves(user); - - await editUser(req, res, next); - - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); -}); - -describe("Auth Controller - checkSuperadminExists", function() { - let req, res, next; - - beforeEach(function() { - req = { - db: { - checkSuperadmin: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if checkSuperadmin fails", async function() { - req.db.checkSuperadmin.rejects(new Error("checkSuperadmin error")); - await checkSuperadminExists(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error"); - }); - - it("should return true if a superadmin exists", async function() { - req.db.checkSuperadmin.resolves(true); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: true, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should return false if a superadmin does not exist", async function() { - req.db.checkSuperadmin.resolves(false); - await checkSuperadminExists(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.AUTH_SUPERADMIN_EXISTS, - data: false, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - requestRecovery", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: { email: "test@test.com" }, - db: { - getUserByEmail: sinon.stub(), - requestRecoveryToken: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub().returns({ clientHost: "http://localhost" }), - }, - emailService: { - buildAndSendEmail: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function() { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getUserByEmail fails", async function() { - req.db.getUserByEmail.rejects(new Error("getUserByEmail error")); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getUserByEmail error"); - }); - - it("should throw an error if the user is not found", async function() { - req.db.getUserByEmail.resolves(null); - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - // expect(next.firstCall.args[0].message).to.equal( - // errorMessages.FRIENDLY_ERROR - // ); - }); - - it("should throw an error if the email is not provided", async function() { - req.body = {}; - await requestRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if the email is provided", async function() { - const user = { firstName: "John" }; - const recoveryToken = { token: "recovery-token" }; - const msgId = "message-id"; - req.db.getUserByEmail.resolves(user); - req.db.requestRecoveryToken.resolves(recoveryToken); - req.emailService.buildAndSendEmail.resolves(msgId); - await requestRecovery(req, res, next); - expect(req.db.getUserByEmail.calledOnceWith("test@test.com")).to.be.true; - expect(req.db.requestRecoveryToken.calledOnceWith(req, res)).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledOnceWith( - "passwordResetTemplate", - { - name: "John", - email: "test@test.com", - url: "http://localhost/set-new-password/recovery-token", - }, - "test@test.com", - "Checkmate Password Reset" - ) - ).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN, - data: msgId, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - validateRecovery", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: { recoveryToken: "recovery-token" }, - db: { - validateRecoveryToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function() { - req.body = {}; - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if validateRecoveryToken fails", async function() { - req.db.validateRecoveryToken.rejects(new Error("validateRecoveryToken error")); - await validateRecovery(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("validateRecoveryToken error"); - }); - - it("should return a success message if the token is valid", async function() { - req.db.validateRecoveryToken.resolves(); - await validateRecovery(req, res, next); - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - resetPassword", function() { - let req, res, next, newPasswordValidation, handleValidationError, handleError; - - beforeEach(function() { - req = { - body: { - recoveryToken: "recovery-token", - password: "Password1!", - }, - db: { - resetPassword: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - newPasswordValidation = { - validateAsync: sinon.stub(), - }; - handleValidationError = sinon.stub(); - handleError = sinon.stub(); - }); - - it("should reject with an error if validation fails", async function() { - req.body = { password: "bad_password" }; - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if resetPassword fails", async function() { - const error = new Error("resetPassword error"); - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.rejects(error); - await resetPassword(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("resetPassword error"); - }); - - it("should reset password successfully", async function() { - const user = { _doc: {} }; - const appSettings = { jwtSecret: "my_secret" }; - const token = "token"; - - newPasswordValidation.validateAsync.resolves(); - req.db.resetPassword.resolves(user); - req.settingsService.getSettings.resolves(appSettings); - - await resetPassword(req, res, next); - - expect(req.db.resetPassword.calledOnceWith(req, res)).to.be.true; - expect(req.settingsService.getSettings.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_RESET_PASSWORD, - data: { user: sinon.match.object, token: sinon.match.string }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - deleteUser", function() { - let req, res, next, handleError; - - beforeEach(function() { - req = { - headers: { - authorization: "Bearer token", - }, - db: { - getUserByEmail: sinon.stub(), - getMonitorsByTeamId: sinon.stub(), - deleteJob: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - deleteTeam: sinon.stub(), - deleteAllOtherUsers: sinon.stub(), - deleteMonitorsByUserId: sinon.stub(), - deleteUser: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - - sinon.stub(jwt, "decode"); - - handleError = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should throw an error if user is not found", async function() { - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND)); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal(errorMessages.DB_USER_NOT_FOUND); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete user and associated data if user is superadmin", async function() { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["superadmin"], - teamId: "team_id", - }; - const monitors = [{ _id: "monitor_id" }]; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - req.db.getMonitorsByTeamId.resolves({ monitors }); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.jobQueue.deleteJob.calledOnceWith(monitors[0])).to.be.true; - expect(req.db.deleteChecks.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deletePageSpeedChecksByMonitorId.calledOnceWith("monitor_id")).to.be - .true; - expect(req.db.deleteNotificationsByMonitorId.calledOnceWith("monitor_id")).to.be.true; - expect(req.db.deleteTeam.calledOnceWith("team_id")).to.be.true; - expect(req.db.deleteAllOtherUsers.calledOnce).to.be.true; - expect(req.db.deleteMonitorsByUserId.calledOnceWith("user_id")).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should delete user if user is not superadmin", async function() { - const user = { - _id: "user_id", - email: "test@example.com", - role: ["user"], - teamId: "team_id", - }; - - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.resolves(user); - - await deleteUser(req, res, next); - - expect(req.db.getUserByEmail.calledOnceWith("test@example.com")).to.be.true; - expect( - req.db.getMonitorsByTeamId.calledOnceWith({ - params: { teamId: "team_id" }, - }) - ).to.be.true; - expect(req.db.deleteUser.calledOnceWith("user_id")).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.AUTH_DELETE_USER, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should handle errors", async function() { - const error = new Error("Something went wrong"); - const SERVICE_NAME = "AuthController"; - jwt.decode.returns({ email: "test@example.com" }); - req.db.getUserByEmail.rejects(error); - await deleteUser(req, res, next); - expect(next.calledOnce).to.be.true; - expect(next.firstCall.args[0].message).to.equal("Something went wrong"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Auth Controller - getAllUsers", function() { - let req, res, next; - - beforeEach(function() { - req = { - db: { - getAllUsers: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); // Restore the original methods after each test - }); - - it("should return 200 and all users", async function() { - const allUsers = [{ id: 1, name: "John Doe" }]; - req.db.getAllUsers.resolves(allUsers); - - await getAllUsers(req, res, next); - - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: "Got all users", - data: allUsers, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error when an exception occurs", async function() { - const error = new Error("Something went wrong"); - req.db.getAllUsers.rejects(error); - await getAllUsers(req, res, next); - expect(req.db.getAllUsers.calledOnce).to.be.true; - expect(next.calledOnce).to.be.true; - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); diff --git a/Server/tests/controllers/checkController.test.js b/Server/tests/controllers/checkController.test.js deleted file mode 100644 index 0c00b9487..000000000 --- a/Server/tests/controllers/checkController.test.js +++ /dev/null @@ -1,379 +0,0 @@ -import { - createCheck, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -} from "../../controllers/checkController.js"; -import jwt from "jsonwebtoken"; -import { errorMessages, successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; -describe("Check Controller - createCheck", function() { - let req, res, next, handleError; - - beforeEach(function() { - req = { - params: {}, - body: {}, - db: { - createCheck: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - handleError = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); // Restore the original methods after each test - }); - - it("should reject with a validation if params are invalid", async function() { - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with a validation error if body is invalid", async function() { - req.params = { - monitorId: "monitorId", - }; - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function() { - req.params = { - monitorId: "monitorId", - }; - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - req.db.createCheck.rejects(new Error("error")); - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should return a success message if check is created", async function() { - req.params = { - monitorId: "monitorId", - }; - req.db.createCheck.resolves({ id: "123" }); - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - await createCheck(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_CREATE, - data: { id: "123" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); -}); - -describe("Check Controller - getChecks", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - query: {}, - db: { - getChecks: sinon.stub(), - getChecksCount: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function() { - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return a success message if checks are found", async function() { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.resolves([{ id: "123" }]); - req.db.getChecksCount.resolves(1); - await getChecks(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount: 1, checks: [{ id: "123" }] }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function() { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.rejects(new Error("error")); - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); -}); - -describe("Check Controller - getTeamChecks", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - query: {}, - db: { - getTeamChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with a validation error if params are invalid", async function() { - await getTeamChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should return 200 and check data on successful validation and data retrieval", async function() { - req.params = { teamId: "1" }; - const checkData = [{ id: 1, name: "Check 1" }]; - req.db.getTeamChecks.resolves(checkData); - - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }) - ).to.be.true; - }); - - it("should call next with error if data retrieval fails", async function() { - req.params = { teamId: "1" }; - req.db.getTeamChecks.rejects(new Error("Retrieval Error")); - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); -}); - -describe("Check Controller - deleteChecks", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - db: { - deleteChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - await deleteChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function() { - req.params = { monitorId: "1" }; - req.db.deleteChecks.rejects(new Error("Deletion Error")); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function() { - req.params = { monitorId: "123" }; - req.db.deleteChecks.resolves(1); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - deleteChecksByTeamId", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - db: { - deleteChecksByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - await deleteChecksByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should call next with error if data retrieval fails", async function() { - req.params = { teamId: "1" }; - req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error")); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); - - it("should delete checks successfully", async function() { - req.params = { teamId: "123" }; - req.db.deleteChecksByTeamId.resolves(1); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); -}); - -describe("Check Controller - updateCheckTTL", function() { - let stub, req, res, next; - - beforeEach(function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - - req = { - body: {}, - headers: { authorization: "Bearer token" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - updateChecksTTL: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - stub.restore(); - }); - - it("should reject if body validation fails", async function() { - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should throw a JwtError if verification fails", async function() { - stub.restore(); - req.body = { - ttl: 1, - }; - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should call next with error if data retrieval fails", async function() { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.rejects(new Error("Update Error")); - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); - - it("should update TTL successfully", async function() { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.resolves(); - await updateChecksTTL(req, res, next); - expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }) - ).to.be.true; - }); -}); diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js deleted file mode 100644 index cb298e8a1..000000000 --- a/Server/tests/controllers/controllerUtils.test.js +++ /dev/null @@ -1,161 +0,0 @@ -import sinon from "sinon"; - -import { - handleValidationError, - handleError, - fetchMonitorCertificate, -} from "../../controllers/controllerUtils.js"; -import { expect } from "chai"; -import sslChecker from "ssl-checker"; -import { afterEach } from "node:test"; -import exp from "constants"; - -describe("controllerUtils - handleValidationError", function() { - it("should set status to 422", function() { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.status).to.equal(422); - }); - - it("should set service to the provided serviceName", function() { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.service).to.equal(serviceName); - }); - - it("should set message to error.details[0].message if present", function() { - const error = { - details: [{ message: "Detail message" }], - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Detail message"); - }); - - it("should set message to error.message if error.details is not present", function() { - const error = { - message: "Error message", - }; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Error message"); - }); - - it('should set message to "Validation Error" if neither error.details nor error.message is present', function() { - const error = {}; - const serviceName = "TestService"; - const result = handleValidationError(error, serviceName); - expect(result.message).to.equal("Validation Error"); - }); -}); - -describe("controllerUtils - handleError", function() { - it("should set stats to the provided status if error.code is undefined", function() { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(status); - }); - - it("should not overwrite error.code if it is already defined", function() { - const error = { status: 404 }; - const serviceName = "TestService"; - const method = "testMethod"; - const status = 400; - const result = handleError(error, serviceName, method, status); - expect(result.status).to.equal(404); - }); - - it("should set service to the provided serviceName if error.service is undefined", function() { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal(serviceName); - }); - - it("should not overwrite error.service if it is already defined", function() { - const error = { service: "ExistingService" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.service).to.equal("ExistingService"); - }); - - it("should set method to the provided method if error.method is undefined", function() { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal(method); - }); - - it("should not overwrite error.method if it is already defined", function() { - const error = { method: "existingMethod" }; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.method).to.equal("existingMethod"); - }); - - it("should set code to 500 if error.code is undefined and no code is provided", function() { - const error = {}; - const serviceName = "TestService"; - const method = "testMethod"; - const result = handleError(error, serviceName, method); - expect(result.status).to.equal(500); - }); -}); - -describe("controllerUtils - fetchMonitorCertificate", function() { - let sslChecker, monitor; - - beforeEach(function() { - monitor = { - url: "https://www.google.com", - }; - sslChecker = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if a URL does not parse", async function() { - monitor.url = "invalidurl"; - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Invalid URL"); - } - }); - - it("should reject with an error if sslChecker throws an error", async function() { - sslChecker.rejects(new Error("Test error")); - try { - await fetchMonitorCertificate(sslChecker, monitor); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Test error"); - } - }); - - it("should return a certificate if sslChecker resolves", async function() { - sslChecker.resolves({ validTo: "2022-01-01" }); - const result = await fetchMonitorCertificate(sslChecker, monitor); - expect(result).to.deep.equal({ validTo: "2022-01-01" }); - }); - - it("should throw an error if a ssl-checker returns null", async function() { - sslChecker.returns(null); - await fetchMonitorCertificate(sslChecker, monitor).catch((error) => { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Certificate not found"); - }); - }); -}); diff --git a/Server/tests/controllers/inviteController.test.js b/Server/tests/controllers/inviteController.test.js deleted file mode 100644 index 389c4f06d..000000000 --- a/Server/tests/controllers/inviteController.test.js +++ /dev/null @@ -1,205 +0,0 @@ -import { - issueInvitation, - inviteVerifyController, -} from "../../controllers/inviteController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import joi from "joi"; -describe("inviteController - issueInvitation", function() { - let req, res, next, stub; - - beforeEach(function() { - req = { - headers: { authorization: "Bearer token" }, - body: { - email: "test@test.com", - role: ["admin"], - teamId: "123", - }, - db: { requestInviteToken: sinon.stub() }, - settingsService: { getSettings: sinon.stub() }, - emailService: { buildAndSendEmail: sinon.stub() }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if role validation fails", async function() { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["bad_role"], firstname: "first_name", teamId: "1" }; - }); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.body = {}; - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function() { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.db.requestInviteToken.throws(new Error("DB error")); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should send an invite successfully", async function() { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); - - it("should send an email successfully", async function() { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - - await issueInvitation(req, res, next); - expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledWith( - "employeeActivationTemplate", - { - name: "John", - link: "http://localhost/register/inviteToken", - }, - "test@test.com", - "Welcome to Uptime Monitor" - ) - ).to.be.true; - stub.restore(); - }); - - it("should continue executing if sending an email fails", async function() { - const token = "token"; - req.emailService.buildAndSendEmail.rejects(new Error("Email error")); - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; - - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("inviteController - inviteVerifyController", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: { token: "token" }, - db: { - getInviteToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = {}; - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getInviteToken.throws(new Error("DB error")); - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 200 and invite data when validation and invite retrieval are successful", async function() { - req.db.getInviteToken.resolves({ invite: "data" }); - await inviteVerifyController(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - status: "success", - msg: "Invite verified", - data: { invite: "data" }, - }) - ).to.be.true; - expect(next.called).to.be.false; - }); -}); diff --git a/Server/tests/controllers/maintenanceWindowController.test.js b/Server/tests/controllers/maintenanceWindowController.test.js deleted file mode 100644 index 974bc07be..000000000 --- a/Server/tests/controllers/maintenanceWindowController.test.js +++ /dev/null @@ -1,422 +0,0 @@ -import { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, -} from "../../controllers/maintenanceWindowController.js"; - -import jwt from "jsonwebtoken"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("maintenanceWindowController - createMaintenanceWindows", function() { - let req, res, next, stub; - - beforeEach(function() { - req = { - body: { - monitors: ["66ff52e7c5911c61698ac724"], - name: "window", - active: true, - start: "2024-10-11T05:27:13.747Z", - end: "2024-10-11T05:27:14.747Z", - repeat: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - createMaintenanceWindow: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.body = {}; - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); - - it("should reject with an error if jwt.verify fails", async function() { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error DB operations fail", async function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.createMaintenanceWindow.throws(new Error("DB error")); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message if all operations are successful", async function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); - - it("should return success message if all operations are successful with active set to undefined", async function() { - req.body.active = undefined; - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowById", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: {}, - params: { - id: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if param validation fails", async function() { - req.params = {}; - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if DB operations fail", async function() { - req.db.getMaintenanceWindowById.throws(new Error("DB error")); - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function() { - req.db.getMaintenanceWindowById.returns({ id: "123" }); - await getMaintenanceWindowById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: { id: "123" }, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", function() { - let req, res, next, stub; - - beforeEach(function() { - req = { - body: {}, - params: {}, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - it("should reject if query validation fails", async function() { - req.query = { - invalid: 1, - }; - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if jwt.verify fails", async function() { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); - - it("should reject with an error if DB operations fail", async function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error")); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - - it("should return success message with data if all operations are successful", async function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: [{ id: jwt.verify().teamId }], - }) - ).to.be.true; - stub.restore(); - }); -}); - -describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: {}, - params: { - monitorId: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByMonitorId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject if param validation fails", async function() { - req.params = {}; - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error")); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function() { - const data = [{ monitorId: "123" }]; - req.db.getMaintenanceWindowsByMonitorId.returns(data); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR, - data: data, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - deleteMaintenanceWindow", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: {}, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - deleteMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject if param validation fails", async function() { - req.params = {}; - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.deleteMaintenanceWindowById.throws(new Error("DB error")); - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message if all operations are successful", async function() { - await deleteMaintenanceWindow(req, res, next); - expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }) - ).to.be.true; - }); -}); - -describe("maintenanceWindowController - editMaintenanceWindow", function() { - let req, res, next; - - beforeEach(function() { - req = { - body: { - active: true, - name: "test", - }, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - editMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject if param validation fails", async function() { - req.params = {}; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject if body validation fails", async function() { - req.body = { invalid: 1 }; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.editMaintenanceWindowById.throws(new Error("DB error")); - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message with data if all operations are successful", async function() { - const data = { id: "123" }; - req.db.editMaintenanceWindowById.returns(data); - - await editMaintenanceWindow(req, res, next); - expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: data, - }) - ).to.be.true; - }); -}); diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js deleted file mode 100644 index 33440d897..000000000 --- a/Server/tests/controllers/monitorController.test.js +++ /dev/null @@ -1,1124 +0,0 @@ -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorCertificate, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - checkEndpointResolution, - deleteMonitor, - deleteAllMonitors, - editMonitor, - pauseMonitor, - addDemoMonitors, -} from "../../controllers/monitorController.js"; -import jwt from "jsonwebtoken"; -import sinon from "sinon"; -import { successMessages } from "../../utils/messages.js"; -import logger from "../../utils/logger.js"; -import axios from "axios"; -const SERVICE_NAME = "monitorController"; - -describe("Monitor Controller - getAllMonitors", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitors: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getAllMonitors.throws(new Error("DB error")); - await getAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = [{ monitor: "data" }]; - req.db.getAllMonitors.returns(data); - await getAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); -describe("Monitor Controller - getAllMonitorsWithUptimeStats", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - query: {}, - body: {}, - db: { - getAllMonitorsWithUptimeStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getAllMonitorsWithUptimeStats.throws(new Error("DB error")); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = [{ monitor: "data" }]; - req.db.getAllMonitorsWithUptimeStats.returns(data); - await getAllMonitorsWithUptimeStats(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_ALL, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorStatsById", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorStatsById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function() { - req.query = { invalid: 1 }; - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getMonitorStatsById.throws(new Error("DB error")); - await getMonitorStatsById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = [{ monitorStats: "data" }]; - req.db.getMonitorStatsById.returns(data); - await getMonitorStatsById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_STATS_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorCertificate", function() { - let req, res, next, fetchMonitorCertificate; - - beforeEach(function() { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - fetchMonitorCertificate = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function() { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorCertificate(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed with a valid cert", async function() { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - const data = { certificate: "cert", validTo: "2024/08/08" }; - fetchMonitorCertificate.returns(data); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CERTIFICATE, - data: { certificateDate: new Date(data.validTo) }, - }) - ).to.be.true; - }); - - it("should return an error if fetchMonitorCertificate fails", async function() { - req.db.getMonitorById.returns({ url: "https://www.google.com" }); - fetchMonitorCertificate.throws(new Error("Certificate error")); - await getMonitorCertificate(req, res, next, fetchMonitorCertificate); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Certificate error"); - }); -}); - -describe("Monitor Controller - getMonitorById", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query param validation fails", async function() { - req.query = { invalid: 1 }; - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getMonitorById.throws(new Error("DB error")); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return 404 if a monitor is not found", async function() { - const error = new Error("Monitor not found"); - error.status = 404; - req.db.getMonitorById.throws(error); - await getMonitorById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(404); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = { monitor: "data" }; - req.db.getMonitorById.returns(data); - await getMonitorById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_ID, - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsAndSummaryByTeamId", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsAndSummaryByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function() { - req.query = { invalid: 1 }; - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error")); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = { monitors: "data", summary: "data" }; - req.db.getMonitorsAndSummaryByTeamId.returns(data); - await getMonitorsAndSummaryByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - getMonitorsByTeamId", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: { - teamId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if query validation fails", async function() { - req.query = { invalid: 1 }; - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB operations fail", async function() { - req.db.getMonitorsByTeamId.throws(new Error("DB error")); - await getMonitorsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const data = { monitors: "data" }; - req.db.getMonitorsByTeamId.returns(data); - await getMonitorsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), - data: data, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - createMonitor", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - query: {}, - body: { - userId: "123", - teamId: "123", - name: "test_monitor", - description: "test_monitor_desc", - type: "http", - url: "https://example.com", - notifications: [{ email: "example@example.com" }], - }, - db: { - createMonitor: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = {}; - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB createMonitor operation fail", async function() { - req.db.createMonitor.throws(new Error("DB error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if DB createNotification operation fail", async function() { - req.db.createNotification.throws(new Error("DB error")); - req.db.createMonitor.returns({ _id: "123" }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if monitor.save operation fail", async function() { - req.db.createMonitor.returns({ - _id: "123", - save: sinon.stub().throws(new Error("Monitor save error")), - }); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Monitor save error"); - }); - - it("should throw an error if addJob operation fails", async function() { - req.db.createMonitor.returns({ _id: "123", save: sinon.stub() }); - req.jobQueue.addJob.throws(new Error("Job error")); - await createMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should return success message and data if all operations succeed", async function() { - const monitor = { _id: "123", save: sinon.stub() }; - req.db.createMonitor.returns(monitor); - await createMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_CREATE, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - checkEndpointResolution", function() { - let req, res, next, axiosGetStub; - - beforeEach(function() { - req = { query: { monitorURL: "https://example.com" } }; - res = { status: sinon.stub().returnsThis(), json: sinon.stub() }; - next = sinon.stub(); - axiosGetStub = sinon.stub(axios, "get"); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should resolve the URL successfully", async function() { - axiosGetStub.resolves({ status: 200, statusText: "OK" }); - await checkEndpointResolution(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - code: 200, - statusText: "OK", - msg: "URL resolved successfully", - }) - ).to.be.true; - expect(next.called).to.be.false; - }); - - it("should return an error if endpoint resolution fails", async function() { - const axiosError = new Error("resolution failed"); - axiosError.code = "ENOTFOUND"; - axiosGetStub.rejects(axiosError); - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const errorPassedToNext = next.getCall(0).args[0]; - expect(errorPassedToNext).to.be.an.instanceOf(Error); - expect(errorPassedToNext.message).to.include("resolution failed"); - expect(errorPassedToNext.code).to.equal("ENOTFOUND"); - expect(errorPassedToNext.status).to.equal(500); - }); - - it("should reject with an error if query validation fails", async function() { - req.query.monitorURL = "invalid-url"; - await checkEndpointResolution(req, res, next); - expect(next.calledOnce).to.be.true; - const error = next.getCall(0).args[0]; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - expect(error.message).to.equal('"monitorURL" must be a valid uri'); - }); -}); - -describe("Monitor Controller - deleteMonitor", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteMonitor: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if DB deleteMonitor operation fail", async function() { - req.db.deleteMonitor.throws(new Error("DB error")); - await deleteMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteJob throws an error", async function() { - const error = new Error("Job error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.jobQueue.deleteJob.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` - ); - }); - - it("should log an error if deleteChecks throws an error", async function() { - const error = new Error("Checks error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteChecks.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` - ); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function() { - const error = new Error("PageSpeed error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` - ); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function() { - const error = new Error("Notifications error"); - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteMonitor(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` - ); - }); - - it("should return success message if all operations succeed", async function() { - const monitor = { name: "test_monitor", _id: "123" }; - req.db.deleteMonitor.returns(monitor); - await deleteMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DELETE, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - deleteAllMonitors", function() { - let req, res, next, stub; - - beforeEach(function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - deleteAllMonitors: sinon.stub(), - deleteChecks: sinon.stub(), - deletePageSpeedChecksByMonitorId: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - sinon.stub(logger, "error"); - }); - - afterEach(function() { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders throws an error", async function() { - req.headers = {}; - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if token validation fails", async function() { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if DB deleteAllMonitors operation fail", async function() { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.throws(new Error("DB error")); - await deleteAllMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should log an error if deleteChecks throws an error", async function() { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Check error"); - req.db.deleteChecks.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function() { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Pagespeed Check error"); - req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should log an error if deleteNotificationsByMonitorId throws an error", async function() { - const monitors = [{ name: "test_monitor", _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); - const error = new Error("Notifications Check error"); - req.db.deleteNotificationsByMonitorId.rejects(error); - await deleteAllMonitors(req, res, next); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal( - `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` - ); - }); - - it("should return success message if all operations succeed", async function() { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.deleteAllMonitors.returns({ - monitors: [{ name: "test_monitor", _id: "123" }], - deletedCount: 1, - }); - await deleteAllMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: "Deleted 1 monitors", - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - editMonitor", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: { - notifications: [{ email: "example@example.com" }], - }, - db: { - getMonitorById: sinon.stub(), - editMonitor: sinon.stub(), - deleteNotificationsByMonitorId: sinon.stub(), - createNotification: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = { invalid: 1 }; - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function() { - req.db.getMonitorById.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if editMonitor operation fails", async function() { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteNotificationsByMonitorId operation fails", async function() { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.deleteNotificationsByMonitorId.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if createNotification operation fails", async function() { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.db.createNotification.throws(new Error("DB error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function() { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.deleteJob.throws(new Error("Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Job error"); - }); - - it("should reject with an error if addJob operation fails", async function() { - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns({ _id: "123" }); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await editMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function() { - const monitor = { _id: "123" }; - req.db.getMonitorById.returns({ teamId: "123" }); - req.db.editMonitor.returns(monitor); - await editMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_EDIT, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - pauseMonitor", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: { - monitorId: "123", - }, - query: {}, - body: {}, - db: { - getMonitorById: sinon.stub(), - }, - jobQueue: { - deleteJob: sinon.stub(), - addJob: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should reject with an error if param validation fails", async function() { - req.params = {}; - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if getMonitorById operation fails", async function() { - req.db.getMonitorById.throws(new Error("DB error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if deleteJob operation fails", async function() { - const monitor = { _id: req.params.monitorId, isActive: true }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.deleteJob.throws(new Error("Delete Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Delete Job error"); - }); - - it("should reject with an error if addJob operation fails", async function() { - const monitor = { _id: req.params.monitorId, isActive: false }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should reject with an error if monitor.save operation fails", async function() { - const monitor = { - _id: req.params.monitorId, - active: false, - save: sinon.stub().throws(new Error("Save error")), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Save error"); - }); - - it("should return success pause message with data if all operations succeed with inactive monitor", async function() { - const monitor = { - _id: req.params.monitorId, - isActive: false, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_PAUSE, - data: monitor, - }) - ).to.be.true; - }); - - it("should return success resume message with data if all operations succeed with active monitor", async function() { - const monitor = { - _id: req.params.monitorId, - isActive: true, - save: sinon.stub().resolves(), - }; - req.db.getMonitorById.returns(monitor); - await pauseMonitor(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_RESUME, - data: monitor, - }) - ).to.be.true; - }); -}); - -describe("Monitor Controller - addDemoMonitors", function() { - let req, res, next, stub; - - beforeEach(function() { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { _id: "123", teamId: "123" }; - }); - req = { - headers: { - authorization: "Bearer token", - }, - params: {}, - query: {}, - body: {}, - db: { - addDemoMonitors: sinon.stub(), - }, - settingsService: { - getSettings: sinon.stub(), - }, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - stub.restore(); - }); - - it("should reject with an error if getTokenFromHeaders fails", async function() { - req.headers = {}; - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("No auth headers"); - expect(next.firstCall.args[0].status).to.equal(500); - }); - - it("should reject with an error if getting settings fails", async function() { - req.settingsService.getSettings.throws(new Error("Settings error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Settings error"); - }); - - it("should reject with an error if JWT validation fails", async function() { - stub.restore(); - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); - - it("should reject with an error if addDemoMonitors operation fails", async function() { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.throws(new Error("DB error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - - it("should reject with an error if addJob operation fails", async function() { - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns([{ _id: "123" }]); - req.jobQueue.addJob.throws(new Error("Add Job error")); - await addDemoMonitors(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Add Job error"); - }); - - it("should return success message with data if all operations succeed", async function() { - const monitors = [{ _id: "123" }]; - req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - req.db.addDemoMonitors.returns(monitors); - await addDemoMonitors(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MONITOR_DEMO_ADDED, - data: monitors.length, - }) - ).to.be.true; - }); -}); diff --git a/Server/tests/controllers/queueController.test.js b/Server/tests/controllers/queueController.test.js deleted file mode 100644 index b04504186..000000000 --- a/Server/tests/controllers/queueController.test.js +++ /dev/null @@ -1,181 +0,0 @@ -import { afterEach } from "node:test"; -import { - getMetrics, - getJobs, - addJob, - obliterateQueue, -} from "../../controllers/queueController.js"; -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Queue Controller - getMetrics", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getMetrics: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getMetrics throws an error", async function() { - req.jobQueue.getMetrics.throws(new Error("getMetrics error")); - await getMetrics(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getMetrics error"); - }); - - it("should return a success message and data if getMetrics is successful", async function() { - const data = { data: "metrics" }; - req.jobQueue.getMetrics.returns(data); - await getMetrics(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - getJobs", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getJobStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if getJobs throws an error", async function() { - req.jobQueue.getJobStats.throws(new Error("getJobs error")); - await getJobs(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getJobs error"); - }); - - it("should return a success message and data if getJobs is successful", async function() { - const data = { data: "jobs" }; - req.jobQueue.getJobStats.returns(data); - await getJobs(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); -}); - -describe("Queue Controller - addJob", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if addJob throws an error", async function() { - req.jobQueue.addJob.throws(new Error("addJob error")); - await addJob(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("addJob error"); - }); - - it("should return a success message if addJob is successful", async function() { - req.jobQueue.addJob.resolves(); - await addJob(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - }); -}); - -describe("Queue Controller - obliterateQueue", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - obliterate: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if obliterateQueue throws an error", async function() { - req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); - await obliterateQueue(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); - }); - - it("should return a success message if obliterateQueue is successful", async function() { - req.jobQueue.obliterate.resolves(); - await obliterateQueue(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_OBLITERATE, - }); - }); -}); diff --git a/Server/tests/controllers/settingsController.test.js b/Server/tests/controllers/settingsController.test.js deleted file mode 100644 index 665a13938..000000000 --- a/Server/tests/controllers/settingsController.test.js +++ /dev/null @@ -1,112 +0,0 @@ -import { afterEach } from "node:test"; -import { - getAppSettings, - updateAppSettings, -} from "../../controllers/settingsController.js"; - -import { successMessages } from "../../utils/messages.js"; -import sinon from "sinon"; - -describe("Settings Controller - getAppSettings", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if getSettings throws an error", async function() { - req.settingsService.getSettings.throws(new Error("getSettings error")); - await getAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getSettings error"); - }); - - it("should return a success message and data if getSettings is successful", async function() { - const data = { data: "settings" }; - req.settingsService.getSettings.returns(data); - await getAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data, - }); - }); -}); - -describe("Settings Controller - updateAppSettings", function() { - let req, res, next; - - beforeEach(function() { - req = { - headers: {}, - params: {}, - body: {}, - db: { - updateAppSettings: sinon.stub(), - }, - settingsService: { - reloadSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should reject with an error if body validation fails", async function() { - req.body = { invalid: 1 }; - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - - it("should reject with an error if updateAppSettings throws an error", async function() { - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - - it("should reject with an error if reloadSettings throws an error", async function() { - req.settingsService.reloadSettings.throws(new Error("reloadSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("reloadSettings error"); - }); - - it("should return a success message and data if updateAppSettings is successful", async function() { - const data = { data: "settings" }; - req.settingsService.reloadSettings.returns(data); - await updateAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data, - }); - }); -}); diff --git a/Server/tests/controllers/statusPageController.test.js b/Server/tests/controllers/statusPageController.test.js deleted file mode 100644 index e1371194d..000000000 --- a/Server/tests/controllers/statusPageController.test.js +++ /dev/null @@ -1,130 +0,0 @@ -import sinon from "sinon"; -import { - createStatusPage, - getStatusPageByUrl, -} from "../../controllers/statusPageController.js"; - -describe("statusPageController", function() { - let req, res, next; - - beforeEach(function() { - req = { - params: {}, - body: {}, - db: { - createStatusPage: sinon.stub(), - getStatusPageByUrl: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createStatusPage", function() { - beforeEach(function() { - req.body = { - companyName: "Test Company", - url: "123456", - timezone: "America/Toronto", - color: "#000000", - theme: "light", - monitors: ["67309ca673788e808884c8ac", "67309ca673788e808884c8ac"], - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should handle a validation error", async function() { - req.body = { - // Invalid data that will trigger validation error - companyName: "", - url: "", - timezone: "", - color: "invalid", - theme: "invalid", - monitors: ["invalid-id"], - }; - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a db error", async function() { - const err = new Error("DB error"); - req.db.createStatusPage.throws(err); - - try { - await createStatusPage(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should insert a properly formatted status page", async function() { - const result = await createStatusPage(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - }); - }); - - describe("getStatusPageByUrl", function() { - beforeEach(function() { - req.params = { - url: "123456", - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should handle a validation error", async function() { - req.params = { - url: "", - }; - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Validation error"); - } - }); - - it("should handle a DB error", async function() { - const err = new Error("DB error"); - req.db.getStatusPageByUrl.throws(err); - - try { - await getStatusPageByUrl(req, res, next); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page", async function() { - const statusPage = { - _id: "123456", - companyName: "Test Company", - url: "123456", - }; - req.db.getStatusPageByUrl.resolves(statusPage); - const result = await getStatusPageByUrl(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0].success).to.be.true; - expect(res.json.firstCall.args[0].data).to.deep.equal(statusPage); - }); - }); -}); diff --git a/Server/tests/db/checkModule.test.js b/Server/tests/db/checkModule.test.js deleted file mode 100644 index 986fa1457..000000000 --- a/Server/tests/db/checkModule.test.js +++ /dev/null @@ -1,593 +0,0 @@ -import sinon from "sinon"; -import { - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, -} from "../../db/mongo/modules/checkModule.js"; -import Check from "../../db/models/Check.js"; -import Monitor from "../../db/models/Monitor.js"; -import User from "../../db/models/User.js"; -import logger from "../../utils/logger.js"; - -describe("checkModule", function() { - describe("createCheck", function() { - let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub; - const mockMonitor = { - _id: "123", - uptimePercentage: 0.5, - status: true, - save: () => this, - }; - const mockCheck = { active: true }; - - beforeEach(function() { - checkSaveStub = sinon.stub(Check.prototype, "save"); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return undefined early if no monitor is found", async function() { - monitorFindByIdStub.returns(null); - const check = await createCheck({ monitorId: "123" }); - expect(check).to.be.undefined; - }); - - it("should return a check", async function() { - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if status is down", async function() { - mockMonitor.status = false; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined", async function() { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: true }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should return a check if uptimePercentage is undefined and status is down", async function() { - mockMonitor.uptimePercentage = undefined; - monitorFindByIdStub.returns(mockMonitor); - checkSaveStub.returns(mockCheck); - monitorSaveStub.returns(mockMonitor); - const check = await createCheck({ monitorId: "123", status: false }); - expect(check).to.deep.equal(mockCheck); - }); - - it("should monitor save error", async function() { - const err = new Error("Save Error"); - monitorSaveStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should handle errors", async function() { - const err = new Error("DB Error"); - checkCountDocumentsStub.throws(err); - try { - await createCheck({ monitorId: "123" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getChecksCount", function() { - let checkCountDocumentStub; - - beforeEach(function() { - checkCountDocumentStub = sinon.stub(Check, "countDocuments"); - }); - - afterEach(function() { - checkCountDocumentStub.restore(); - }); - - it("should return count with basic monitorId query", async function() { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - checkCountDocumentStub.resolves(5); - - const result = await getChecksCount(req); - - expect(result).to.equal(5); - expect(checkCountDocumentStub.calledOnce).to.be.true; - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - }); - }); - - it("should include dateRange in query when provided", async function() { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "day" }, - }; - checkCountDocumentStub.resolves(3); - - const result = await getChecksCount(req); - - expect(result).to.equal(3); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.property("createdAt"); - expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte"); - }); - - it('should handle "all" filter correctly', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "down" filter correctly', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - checkCountDocumentStub.resolves(2); - - const result = await getChecksCount(req); - - expect(result).to.equal(2); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it('should handle "resolve" filter correctly', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - statusCode: 5000, - }); - }); - - it("should handle unknown filter correctly", async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - checkCountDocumentStub.resolves(1); - - const result = await getChecksCount(req); - - expect(result).to.equal(1); - expect(checkCountDocumentStub.firstCall.args[0]).to.deep.equal({ - monitorId: "test123", - status: false, - }); - }); - - it("should combine dateRange and filter in query", async function() { - const req = { - params: { monitorId: "test123" }, - query: { - dateRange: "week", - filter: "down", - }, - }; - checkCountDocumentStub.resolves(4); - - const result = await getChecksCount(req); - - expect(result).to.equal(4); - expect(checkCountDocumentStub.firstCall.args[0]).to.have.all.keys( - "monitorId", - "createdAt", - "status" - ); - }); - }); - - describe("getChecks", function() { - let checkFindStub, monitorFindStub; - - beforeEach(function() { - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function() { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should return checks with limit query", async function() { - const req = { - params: { monitorId: "test123" }, - query: { limit: 10 }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle pagination correctly", async function() { - const req = { - params: { monitorId: "test123" }, - query: { - page: 2, - rowsPerPage: 10, - }, - }; - - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle dateRange filter", async function() { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getChecks(req); - - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "all" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "down" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "resolve" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it('should handle "unknown" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle ascending sort order", async function() { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getChecks(req); - expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]); - }); - - it("should handle error case", async function() { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getChecks"); - } - }); - }); - - describe("getTeamChecks", function() { - let checkFindStub, checkCountDocumentsStub, monitorFindStub; - const mockMonitors = [{ _id: "123" }]; - - beforeEach(function() { - monitorFindStub = sinon.stub(Monitor, "find").returns({ - select: sinon.stub().returns(mockMonitors), - }); - checkCountDocumentsStub = sinon.stub(Check, "countDocuments").returns(2); - checkFindStub = sinon.stub(Check, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns({ - select: sinon.stub().returns([{ id: 1 }, { id: 2 }]), - }), - }), - }), - }); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return checks with basic monitorId query", async function() { - const req = { - params: { teamId: "test123" }, - query: {}, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle pagination correctly", async function() { - const req = { - params: { monitorId: "test123" }, - query: { limit: 1, page: 2, rowsPerPage: 10 }, - }; - - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle dateRange filter", async function() { - const req = { - params: { monitorId: "test123" }, - query: { dateRange: "week" }, - }; - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "all" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "all" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "down" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "down" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "resolve" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "resolve" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it('should handle "unknown" filter', async function() { - const req = { - params: { monitorId: "test123" }, - query: { filter: "unknown" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle ascending sort order", async function() { - const req = { - params: { monitorId: "test123" }, - query: { sortOrder: "asc" }, - }; - - await getChecks(req); - const result = await getTeamChecks(req); - expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] }); - }); - - it("should handle error case", async function() { - const req = { - params: { monitorId: "test123" }, - query: {}, - }; - - checkFindStub.throws(new Error("Database error")); - - try { - await getTeamChecks(req); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("checkModule"); - expect(error.method).to.equal("getTeamChecks"); - } - }); - }); - - describe("deleteChecks", function() { - let checkDeleteManyStub; - - beforeEach(function() { - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return a value if a check is deleted", async function() { - const result = await deleteChecks("123"); - expect(result).to.equal(1); - }); - - it("should handle an error", async function() { - checkDeleteManyStub.throws(new Error("Database error")); - try { - await deleteChecks("123"); - } catch (error) { - expect(error.message).to.equal("Database error"); - expect(error.method).to.equal("deleteChecks"); - } - }); - }); - - describe("deleteChecksByTeamId", function() { - let mockMonitors = [{ _id: 123, save: () => this }]; - let monitorFindStub, monitorSaveStub, checkDeleteManyStub; - - beforeEach(function() { - monitorSaveStub = sinon.stub(Monitor.prototype, "save"); - monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors); - checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 }); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return a deleted count", async function() { - const result = await deleteChecksByTeamId("123"); - expect(result).to.equal(1); - }); - - it("should handle errors", async function() { - const err = new Error("DB Error"); - monitorFindStub.throws(err); - try { - const result = await deleteChecksByTeamId("123"); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateChecksTTL", function() { - let userUpdateManyStub; - let loggerStub; - - beforeEach(function() { - loggerStub = sinon.stub(logger, "error"); - userUpdateManyStub = sinon.stub(User, "updateMany"); - Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return undefined", async function() { - const result = await updateChecksTTL("123", 10); - expect(result).to.be.undefined; - }); - - it("should log an error if dropIndex throws an error", async function() { - const err = new Error("Drop Index Error"); - Check.collection.dropIndex.throws(err); - await updateChecksTTL("123", 10); - expect(loggerStub.calledOnce).to.be.true; - expect(loggerStub.firstCall.args[0].message).to.equal(err.message); - }); - - it("should throw an error if createIndex throws an error", async function() { - const err = new Error("Create Index Error"); - Check.collection.createIndex.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should throw an error if User.updateMany throws an error", async function() { - const err = new Error("Update Many Error"); - userUpdateManyStub.throws(err); - try { - await updateChecksTTL("123", 10); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/db/hardwareCheckModule.test.js b/Server/tests/db/hardwareCheckModule.test.js deleted file mode 100644 index c0a642b6b..000000000 --- a/Server/tests/db/hardwareCheckModule.test.js +++ /dev/null @@ -1,139 +0,0 @@ -import sinon from "sinon"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import { createHardwareCheck } from "../../db/mongo/modules/hardwareCheckModule.js"; -import Monitor from "../../db/models/Monitor.js"; -import logger from "../../utils/logger.js"; - -const mockHardwareCheck = { - data: { - cpu: { - physical_core: 4, - logical_core: 8, - frequency: 4800, - current_frequency: 1411, - temperature: [45, 50, 46, 47, 45, 50, 46, 47], - free_percent: 0.8552990910595134, - usage_percent: 0.14470090894048657, - }, - memory: { - total_bytes: 16467628032, - available_bytes: 7895044096, - used_bytes: 6599561216, - usage_percent: 0.4008, - }, - disk: [ - { - read_speed_bytes: null, - write_speed_bytes: null, - total_bytes: 931258499072, - free_bytes: 737097256960, - usage_percent: 0.1661, - }, - ], - host: { - os: "linux", - platform: "ubuntu", - kernel_version: "6.8.0-48-generic", - }, - }, - errors: [ - { - metric: ["cpu.temperature"], - err: "unable to read CPU temperature", - }, - ], -}; - -const mockMonitor = { - _id: "123", - uptimePercentage: 1, - status: true, - save: () => this, -}; - -describe("HardwareCheckModule", function() { - let hardwareCheckSaveStub, - hardwareCheckCountDocumentsStub, - monitorFindByIdStub, - loggerStub; - - beforeEach(function() { - loggerStub = sinon.stub(logger, "error"); - hardwareCheckSaveStub = sinon.stub(HardwareCheck.prototype, "save"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - hardwareCheckCountDocumentsStub = sinon.stub(HardwareCheck, "countDocuments"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createHardwareCheck", function() { - it("should return a hardware check", async function() { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: true }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should return a hardware check for a check with status false", async function() { - hardwareCheckSaveStub.resolves(mockHardwareCheck); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckCountDocumentsStub.resolves(1); - const hardwareCheck = await createHardwareCheck({ status: false }); - expect(hardwareCheck).to.exist; - expect(hardwareCheck).to.deep.equal(mockHardwareCheck); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - monitorFindByIdStub.resolves(mockMonitor); - hardwareCheckSaveStub.rejects(err); - try { - await createHardwareCheck({}); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should log an error if a monitor is not found", async function() { - monitorFindByIdStub.resolves(null); - const res = await createHardwareCheck({}); - expect(loggerStub.calledOnce).to.be.true; - expect(res).to.be.null; - }); - - it("should handle a monitor with undefined uptimePercentage", async function() { - monitorFindByIdStub.resolves({ ...mockMonitor, uptimePercentage: undefined }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({}); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and true status", async function() { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: true }); - expect(res).to.exist; - }); - - it("should handle a monitor with undefined uptimePercentage and false status", async function() { - monitorFindByIdStub.resolves({ - ...mockMonitor, - uptimePercentage: undefined, - }); - hardwareCheckSaveStub.resolves(mockHardwareCheck); - hardwareCheckCountDocumentsStub.resolves(1); - const res = await createHardwareCheck({ status: false }); - expect(res).to.exist; - }); - }); -}); diff --git a/Server/tests/db/inviteModule.test.js b/Server/tests/db/inviteModule.test.js deleted file mode 100644 index 164840826..000000000 --- a/Server/tests/db/inviteModule.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import sinon from "sinon"; -import InviteToken from "../../db/models/InviteToken.js"; -import { - requestInviteToken, - getInviteToken, - getInviteTokenAndDelete, -} from "../../db/mongo/modules/inviteModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("Invite Module", function() { - const mockUserData = { - email: "test@test.com", - teamId: "123", - role: ["admin"], - token: "123", - }; - const mockInviteToken = { _id: 123, time: 123 }; - let inviteTokenDeleteManyStub, - inviteTokenSaveStub, - inviteTokenFindOneStub, - inviteTokenFindOneAndDeleteStub; - - beforeEach(function() { - inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany"); - inviteTokenSaveStub = sinon.stub(InviteToken.prototype, "save"); - inviteTokenFindOneStub = sinon.stub(InviteToken, "findOne"); - inviteTokenFindOneAndDeleteStub = sinon.stub(InviteToken, "findOneAndDelete"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("requestInviteToken", function() { - it("should return a new invite token", async function() { - inviteTokenDeleteManyStub.resolves(); - inviteTokenSaveStub.resolves(); - const inviteToken = await requestInviteToken(mockUserData); - expect(inviteToken.email).to.equal(mockUserData.email); - expect(inviteToken.role).to.deep.equal(mockUserData.role); - expect(inviteToken.token).to.exist; - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - inviteTokenDeleteManyStub.rejects(err); - try { - await requestInviteToken(mockUserData); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getInviteToken", function() { - it("should return an invite token", async function() { - inviteTokenFindOneStub.resolves(mockInviteToken); - const inviteToken = await getInviteToken(mockUserData.token); - expect(inviteToken).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function() { - inviteTokenFindOneStub.resolves(null); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function() { - const err = new Error("test error"); - inviteTokenFindOneStub.rejects(err); - try { - await getInviteToken(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteToken"); - } - }); - }); - - describe("getInviteTokenAndDelete", function() { - it("should return a deleted invite", async function() { - inviteTokenFindOneAndDeleteStub.resolves(mockInviteToken); - const deletedInvite = await getInviteTokenAndDelete(mockUserData.token); - expect(deletedInvite).to.deep.equal(mockInviteToken); - }); - - it("should handle a token not found", async function() { - inviteTokenFindOneAndDeleteStub.resolves(null); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error.message).to.equal(errorMessages.AUTH_INVITE_NOT_FOUND); - } - }); - - it("should handle DB errors", async function() { - const err = new Error("test error"); - inviteTokenFindOneAndDeleteStub.rejects(err); - try { - await getInviteTokenAndDelete(mockUserData.token); - } catch (error) { - expect(error).to.deep.equal(err); - expect(error.method).to.equal("getInviteTokenAndDelete"); - } - }); - }); -}); diff --git a/Server/tests/db/maintenanceWindowModule.test.js b/Server/tests/db/maintenanceWindowModule.test.js deleted file mode 100644 index d0abc7fa7..000000000 --- a/Server/tests/db/maintenanceWindowModule.test.js +++ /dev/null @@ -1,283 +0,0 @@ -import sinon from "sinon"; -import MaintenanceWindow from "../../db/models/MaintenanceWindow.js"; -import { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -} from "../../db/mongo/modules/maintenanceWindowModule.js"; - -describe("MaintenanceWindow Module", function() { - const mockMaintenanceWindow = { - monitorId: "123", - active: true, - oneTime: true, - start: 1, - end: 20000, - }; - - let mockMaintenanceWindows = [mockMaintenanceWindow]; - let maintenanceWindowSaveStub, - maintenanceWindowFindByIdStub, - maintenanceWindowCountDocumentsStub, - maintenanceWindowFindStub, - maintenanceWindowFindByIdAndDeleteStub, - maintenanceWindowDeleteManyStub, - maintenanceWindowFindByIdAndUpdateStub; - - beforeEach(function() { - maintenanceWindowSaveStub = sinon.stub(MaintenanceWindow.prototype, "save"); - maintenanceWindowFindByIdStub = sinon.stub(MaintenanceWindow, "findById"); - maintenanceWindowCountDocumentsStub = sinon.stub(MaintenanceWindow, "countDocuments"); - maintenanceWindowFindStub = sinon.stub(MaintenanceWindow, "find").returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMaintenanceWindows), - }), - }), - }); - maintenanceWindowFindByIdAndDeleteStub = sinon.stub( - MaintenanceWindow, - "findByIdAndDelete" - ); - maintenanceWindowDeleteManyStub = sinon.stub(MaintenanceWindow, "deleteMany"); - maintenanceWindowFindByIdAndUpdateStub = sinon.stub( - MaintenanceWindow, - "findByIdAndUpdate" - ); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createMaintenanceWindow", function() { - it("should save a new maintenance window", async function() { - maintenanceWindowSaveStub.resolves(mockMaintenanceWindow); - const result = await createMaintenanceWindow(mockMaintenanceWindow); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowSaveStub.rejects(err); - try { - await createMaintenanceWindow(mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowById", function() { - it("should return a maintenance window", async function() { - maintenanceWindowFindByIdStub.resolves(mockMaintenanceWindow); - const result = await getMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowFindByIdStub.rejects(err); - try { - await getMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByTeamId", function() { - let query; - - beforeEach(function() { - query = { - active: true, - page: 1, - rowsPerPage: 10, - field: "name", - order: "asc", - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return a list of maintenance windows and count", async function() { - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId( - mockMaintenanceWindow.teamId, - query - ); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with empty query", async function() { - query = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId( - mockMaintenanceWindow.teamId, - query - ); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with no pagination provided", async function() { - query.page = undefined; - query.rowsPerPage = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId( - mockMaintenanceWindow.teamId, - query - ); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count with field and desc order", async function() { - query.order = "desc"; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId( - mockMaintenanceWindow.teamId, - query - ); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should return a list of maintenance windows and count no field", async function() { - query.field = undefined; - maintenanceWindowCountDocumentsStub.resolves(1); - const result = await getMaintenanceWindowsByTeamId( - mockMaintenanceWindow.teamId, - query - ); - expect(result).to.deep.equal({ - maintenanceWindows: mockMaintenanceWindows, - maintenanceWindowCount: 1, - }); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowCountDocumentsStub.rejects(err); - try { - await getMaintenanceWindowsByTeamId(mockMaintenanceWindow.teamId, query); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getMaintenanceWindowsByMonitorId", function() { - it("should return a list of maintenance windows", async function() { - maintenanceWindowFindStub.resolves(mockMaintenanceWindows); - const result = await getMaintenanceWindowsByMonitorId( - mockMaintenanceWindow.monitorId - ); - expect(result).to.deep.equal(mockMaintenanceWindows); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowFindStub.rejects(err); - try { - await getMaintenanceWindowsByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowById", function() { - it("should delete a maintenance window", async function() { - maintenanceWindowFindByIdAndDeleteStub.resolves(mockMaintenanceWindow); - const result = await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowFindByIdAndDeleteStub.rejects(err); - try { - await deleteMaintenanceWindowById(mockMaintenanceWindow.id); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByMonitorId", function() { - it("should return the number of documents deleted", async function() { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByMonitorId( - mockMaintenanceWindow.monitorId - ); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByMonitorId(mockMaintenanceWindow.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteMaintenanceWindowByUserId", function() { - it("should return the number of documents deleted", async function() { - maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 }); - const result = await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - expect(result).to.deep.equal({ deletedCount: 1 }); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowDeleteManyStub.rejects(err); - try { - await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("editMaintenanceWindowById", function() { - it("should return the updated maintenance window", async function() { - maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow); - const result = await editMaintenanceWindowById( - mockMaintenanceWindow.id, - mockMaintenanceWindow - ); - expect(result).to.deep.equal(mockMaintenanceWindow); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - maintenanceWindowFindByIdAndUpdateStub.rejects(err); - try { - await editMaintenanceWindowById(mockMaintenanceWindow.id, mockMaintenanceWindow); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/db/monitorModule.test.js b/Server/tests/db/monitorModule.test.js deleted file mode 100644 index 50782494a..000000000 --- a/Server/tests/db/monitorModule.test.js +++ /dev/null @@ -1,1931 +0,0 @@ -import sinon from "sinon"; -import Monitor from "../../db/models/Monitor.js"; -import Check from "../../db/models/Check.js"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import HardwareCheck from "../../db/models/HardwareCheck.js"; -import Notification from "../../db/models/Notification.js"; - -import { errorMessages } from "../../utils/messages.js"; -import { - getAllMonitors, - getAllMonitorsWithUptimeStats, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -} from "../../db/mongo/modules/monitorModule.js"; - -describe("monitorModule", function() { - let monitorFindStub, - monitorFindByIdStub, - monitorFindByIdAndUpdateStub, - monitorFindByIdAndDeleteStub, - monitorDeleteManyStub, - monitorCountStub, - monitorInsertManyStub, - checkFindStub, - pageSpeedCheckFindStub, - hardwareCheckFindStub; - - beforeEach(function() { - monitorFindStub = sinon.stub(Monitor, "find"); - monitorFindByIdStub = sinon.stub(Monitor, "findById"); - monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate"); - monitorFindByIdAndDeleteStub = sinon.stub(Monitor, "findByIdAndDelete"); - monitorDeleteManyStub = sinon.stub(Monitor, "deleteMany"); - monitorCountStub = sinon.stub(Monitor, "countDocuments"); - - monitorInsertManyStub = sinon.stub(Monitor, "insertMany"); - checkFindStub = sinon.stub(Check, "find").returns({ - sort: sinon.stub(), - }); - pageSpeedCheckFindStub = sinon.stub(PageSpeedCheck, "find").returns({ - sort: sinon.stub(), - }); - hardwareCheckFindStub = sinon.stub(HardwareCheck, "find").returns({ - sort: sinon.stub(), - }); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("getAllMonitors", function() { - it("should return all monitors", async function() { - const mockMonitors = [ - { _id: "1", name: "Monitor 1", url: "test1.com" }, - { _id: "2", name: "Monitor 2", url: "test2.com" }, - ]; - monitorFindStub.returns(mockMonitors); - const result = await getAllMonitors(); - - expect(result).to.deep.equal(mockMonitors); - expect(monitorFindStub.calledOnce).to.be.true; - expect(monitorFindStub.firstCall.args).to.deep.equal([]); - }); - - it("should handle empty results", async function() { - monitorFindStub.returns([]); - const result = await getAllMonitors(); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should throw error when database fails", async function() { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getAllMonitors"; - monitorFindStub.rejects(error); - // Act & Assert - try { - await getAllMonitors(); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getAllMonitors"); - } - }); - }); - - describe("getAllMonitorsWithUptimeStats", function() { - it("should return monitors with uptime stats for different time periods", async function() { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [ - { status: true }, - { status: true }, - { status: false }, - { status: true }, - ]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for pagespeed type", async function() { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "pagespeed", - toObject: () => ({ - _id: "monitor1", - type: "pagespeed", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [ - { status: true }, - { status: true }, - { status: false }, - { status: true }, - ]; - - monitorFindStub.resolves(mockMonitors); - pageSpeedCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should return monitors with stats for hardware type", async function() { - // Mock data - const mockMonitors = [ - { - _id: "monitor1", - type: "hardware", - toObject: () => ({ - _id: "monitor1", - type: "hardware", - name: "Test Monitor", - }), - }, - ]; - - const mockChecks = [ - { status: true }, - { status: true }, - { status: false }, - { status: true }, - ]; - - monitorFindStub.resolves(mockMonitors); - hardwareCheckFindStub.resolves(mockChecks); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - - const monitor = result[0]; - expect(monitor).to.have.property("_id", "monitor1"); - expect(monitor).to.have.property("name", "Test Monitor"); - - // Check uptime percentages exist for all time periods - expect(monitor).to.have.property("1"); - expect(monitor).to.have.property("7"); - expect(monitor).to.have.property("30"); - expect(monitor).to.have.property("90"); - - // Verify uptime percentage calculation (3 successful out of 4 = 75%) - expect(monitor["1"]).to.equal(75); - expect(monitor["7"]).to.equal(75); - expect(monitor["30"]).to.equal(75); - expect(monitor["90"]).to.equal(75); - }); - - it("should handle errors appropriately", async function() { - // Setup stub to throw error - monitorFindStub.rejects(new Error("Database error")); - - try { - await getAllMonitorsWithUptimeStats(); - } catch (error) { - expect(error).to.be.an("error"); - expect(error.message).to.equal("Database error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getAllMonitorsWithUptimeStats"); - } - }); - - it("should handle empty monitor list", async function() { - monitorFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(0); - }); - - it("should handle monitor with no checks", async function() { - const mockMonitors = [ - { - _id: "monitor1", - type: "http", - toObject: () => ({ - _id: "monitor1", - type: "http", - name: "Test Monitor", - }), - }, - ]; - - monitorFindStub.resolves(mockMonitors); - checkFindStub.resolves([]); - - const result = await getAllMonitorsWithUptimeStats(); - - expect(result[0]).to.have.property("1", 0); - expect(result[0]).to.have.property("7", 0); - expect(result[0]).to.have.property("30", 0); - expect(result[0]).to.have.property("90", 0); - }); - }); - - describe("calculateUptimeDuration", function() { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function() { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function() { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function() { - expect(calculateUptimeDuration([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(calculateUptimeDuration(null)).to.equal(0); - }); - - it("should calculate uptime from last down check to most recent check", function() { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, // Most recent - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: false, createdAt: "2024-01-01T09:00:00Z" }, // Last down - { status: true, createdAt: "2024-01-01T08:00:00Z" }, - ]; - - // Expected: 2 hours (from 09:00 to 11:00) = 7200000ms - expect(calculateUptimeDuration(checks)).to.equal(7200000); - }); - - it("should calculate uptime from first check when no down checks exist", function() { - const checks = [ - { status: true, createdAt: "2024-01-01T11:00:00Z" }, - { status: true, createdAt: "2024-01-01T10:00:00Z" }, - { status: true, createdAt: "2024-01-01T09:00:00Z" }, - ]; - - // Expected: Current time (12:00) - First check (09:00) = 3 hours = 10800000ms - expect(calculateUptimeDuration(checks)).to.equal(10800000); - }); - }); - - describe("getLastChecked", function() { - let clock; - const NOW = new Date("2024-01-01T12:00:00Z").getTime(); - - beforeEach(function() { - // Fix the current time - clock = sinon.useFakeTimers(NOW); - }); - - afterEach(function() { - clock.restore(); - }); - - it("should return 0 when checks array is empty", function() { - expect(getLastChecked([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(getLastChecked(null)).to.equal(0); - }); - - it("should return time difference between now and most recent check", function() { - const checks = [ - { createdAt: "2024-01-01T11:30:00Z" }, // 30 minutes ago - { createdAt: "2024-01-01T11:00:00Z" }, - { createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Expected: 30 minutes = 1800000ms - expect(getLastChecked(checks)).to.equal(1800000); - }); - - it("should handle checks from different days", function() { - const checks = [ - { createdAt: "2023-12-31T12:00:00Z" }, // 24 hours ago - { createdAt: "2023-12-30T12:00:00Z" }, - ]; - - // Expected: 24 hours = 86400000ms - expect(getLastChecked(checks)).to.equal(86400000); - }); - }); - - describe("getLatestResponseTime", function() { - it("should return 0 when checks array is empty", function() { - expect(getLatestResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(getLatestResponseTime(null)).to.equal(0); - }); - - it("should return response time from most recent check", function() { - const checks = [ - { responseTime: 150, createdAt: "2024-01-01T11:30:00Z" }, // Most recent - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 250, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - expect(getLatestResponseTime(checks)).to.equal(150); - }); - - it("should handle missing responseTime in checks", function() { - const checks = [ - { createdAt: "2024-01-01T11:30:00Z" }, - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - ]; - - expect(getLatestResponseTime(checks)).to.equal(0); - }); - }); - - describe("getAverageResponseTime", function() { - it("should return 0 when checks array is empty", function() { - expect(getAverageResponseTime([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(getAverageResponseTime(null)).to.equal(0); - }); - - it("should calculate average response time from all checks", function() { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 200 + 300) / 3 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should handle missing responseTime in some checks", function() { - const checks = [ - { responseTime: 100, createdAt: "2024-01-01T11:30:00Z" }, - { createdAt: "2024-01-01T11:00:00Z" }, - { responseTime: 300, createdAt: "2024-01-01T10:30:00Z" }, - ]; - - // Average: (100 + 300) / 2 = 200 - expect(getAverageResponseTime(checks)).to.equal(200); - }); - - it("should return 0 when no checks have responseTime", function() { - const checks = [ - { createdAt: "2024-01-01T11:30:00Z" }, - { createdAt: "2024-01-01T11:00:00Z" }, - ]; - - expect(getAverageResponseTime(checks)).to.equal(0); - }); - }); - - describe("getUptimePercentage", function() { - it("should return 0 when checks array is empty", function() { - expect(getUptimePercentage([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(getUptimePercentage(null)).to.equal(0); - }); - - it("should return 100 when all checks are up", function() { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getUptimePercentage(checks)).to.equal(100); - }); - - it("should return 0 when all checks are down", function() { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getUptimePercentage(checks)).to.equal(0); - }); - - it("should calculate correct percentage for mixed status checks", function() { - const checks = [ - { status: true }, - { status: false }, - { status: true }, - { status: true }, - ]; - // 3 up out of 4 total = 75% - expect(getUptimePercentage(checks)).to.equal(75); - }); - - it("should handle undefined status values", function() { - const checks = [{ status: true }, { status: undefined }, { status: true }]; - // 2 up out of 3 total ≈ 66.67% - expect(getUptimePercentage(checks)).to.equal((2 / 3) * 100); - }); - }); - - describe("getIncidents", function() { - it("should return 0 when checks array is empty", function() { - expect(getIncidents([])).to.equal(0); - }); - - it("should return 0 when checks array is null", function() { - expect(getIncidents(null)).to.equal(0); - }); - - it("should return 0 when all checks are up", function() { - const checks = [{ status: true }, { status: true }, { status: true }]; - expect(getIncidents(checks)).to.equal(0); - }); - - it("should count all incidents when all checks are down", function() { - const checks = [{ status: false }, { status: false }, { status: false }]; - expect(getIncidents(checks)).to.equal(3); - }); - - it("should count correct number of incidents for mixed status checks", function() { - const checks = [ - { status: true }, - { status: false }, - { status: true }, - { status: false }, - { status: true }, - ]; - expect(getIncidents(checks)).to.equal(2); - }); - - it("should handle undefined status values", function() { - const checks = [ - { status: true }, - { status: undefined }, - { status: false }, - { status: false }, - ]; - // Only counts explicit false values - expect(getIncidents(checks)).to.equal(2); - }); - }); - - describe("getMonitorChecks", function() { - let mockModel; - - beforeEach(function() { - // Create a mock model with chainable methods - const mockChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-01") }, - { monitorId: "123", createdAt: new Date("2024-01-02") }, - ]; - - mockModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(mockChecks), - }), - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return all checks and date-ranged checks", async function() { - // Arrange - const monitorId = "123"; - const dateRange = { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }; - const sortOrder = -1; - - // Act - const result = await getMonitorChecks(monitorId, mockModel, dateRange, sortOrder); - - // Assert - expect(result).to.have.keys(["checksAll", "checksForDateRange"]); - - // Verify find was called with correct parameters - expect(mockModel.find.firstCall.args[0]).to.deep.equal({ monitorId }); - expect(mockModel.find.secondCall.args[0]).to.deep.equal({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }); - - // Verify sort was called with correct parameters - const sortCalls = mockModel.find().sort.getCalls(); - sortCalls.forEach((call) => { - expect(call.args[0]).to.deep.equal({ createdAt: sortOrder }); - }); - }); - - it("should handle empty results", async function() { - // Arrange - const emptyModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - emptyModel, - { - start: new Date(), - end: new Date(), - }, - -1 - ); - - // Assert - expect(result.checksAll).to.be.an("array").that.is.empty; - expect(result.checksForDateRange).to.be.an("array").that.is.empty; - }); - - it("should maintain sort order", async function() { - // Arrange - const sortedChecks = [ - { monitorId: "123", createdAt: new Date("2024-01-02") }, - { monitorId: "123", createdAt: new Date("2024-01-01") }, - ]; - - const sortedModel = { - find: sinon.stub().returns({ - sort: sinon.stub().returns(sortedChecks), - }), - }; - - // Act - const result = await getMonitorChecks( - "123", - sortedModel, - { - start: new Date("2024-01-01"), - end: new Date("2024-01-02"), - }, - -1 - ); - - // Assert - expect(result.checksAll[0].createdAt).to.be.greaterThan( - result.checksAll[1].createdAt - ); - expect(result.checksForDateRange[0].createdAt).to.be.greaterThan( - result.checksForDateRange[1].createdAt - ); - }); - }); - - describe("processChecksForDisplay", function() { - let normalizeStub; - - beforeEach(function() { - normalizeStub = sinon.stub(); - }); - - it("should return original checks when numToDisplay is not provided", function() { - const checks = [1, 2, 3, 4, 5]; - const result = processChecksForDisplay(normalizeStub, checks); - expect(result).to.deep.equal(checks); - }); - - it("should return original checks when numToDisplay is greater than checks length", function() { - const checks = [1, 2, 3]; - const result = processChecksForDisplay(normalizeStub, checks, 5); - expect(result).to.deep.equal(checks); - }); - - it("should filter checks based on numToDisplay", function() { - const checks = [1, 2, 3, 4, 5, 6]; - const result = processChecksForDisplay(normalizeStub, checks, 3); - // Should return [1, 3, 5] as n = ceil(6/3) = 2 - expect(result).to.deep.equal([1, 3, 5]); - }); - - it("should handle empty checks array", function() { - const checks = []; - const result = processChecksForDisplay(normalizeStub, checks, 3); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should call normalizeData when normalize is true", function() { - const checks = [1, 2, 3]; - normalizeStub.returns([10, 20, 30]); - - const result = processChecksForDisplay(normalizeStub, checks, null, true); - - expect(normalizeStub.args[0]).to.deep.equal([checks, 1, 100]); - expect(result).to.deep.equal([10, 20, 30]); - }); - - it("should handle both filtering and normalization", function() { - const checks = [1, 2, 3, 4, 5, 6]; - normalizeStub.returns([10, 30, 50]); - - const result = processChecksForDisplay(normalizeStub, checks, 3, true); - - expect(normalizeStub.args[0][0]).to.deep.equal([1, 3, 5]); - expect(result).to.deep.equal([10, 30, 50]); - }); - }); - - describe("groupChecksByTime", function() { - const mockChecks = [ - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: "2024-01-15T10:45:15Z" }, - { createdAt: "2024-01-15T11:15:00Z" }, - { createdAt: "2024-01-16T10:30:00Z" }, - ]; - - it("should group checks by hour when dateRange is 'day'", function() { - const result = groupChecksByTime(mockChecks, "day"); - - // Get timestamps for 10:00 and 11:00 on Jan 15 - const time1 = new Date("2024-01-15T10:00:00Z").getTime(); - const time2 = new Date("2024-01-15T11:00:00Z").getTime(); - const time3 = new Date("2024-01-16T10:00:00Z").getTime(); - - expect(Object.keys(result)).to.have.lengthOf(3); - - expect(result[time1].checks).to.have.lengthOf(2); - expect(result[time2].checks).to.have.lengthOf(1); - expect(result[time3].checks).to.have.lengthOf(1); - }); - - it("should group checks by day when dateRange is not 'day'", function() { - const result = groupChecksByTime(mockChecks, "week"); - - expect(Object.keys(result)).to.have.lengthOf(2); - expect(result["2024-01-15"].checks).to.have.lengthOf(3); - expect(result["2024-01-16"].checks).to.have.lengthOf(1); - }); - - it("should handle empty checks array", function() { - const result = groupChecksByTime([], "day"); - expect(result).to.deep.equal({}); - }); - - it("should handle single check", function() { - const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }]; - const result = groupChecksByTime(singleCheck, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - }); - - it("should skip invalid dates and process valid ones", function() { - const checksWithInvalidDate = [ - { createdAt: "invalid-date" }, - { createdAt: "2024-01-15T10:30:45Z" }, - { createdAt: null }, - { createdAt: undefined }, - { createdAt: "" }, - ]; - - const result = groupChecksByTime(checksWithInvalidDate, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(1); - expect(result[expectedTime].checks[0].createdAt).to.equal("2024-01-15T10:30:45Z"); - }); - - it("should handle checks in same time group", function() { - const checksInSameHour = [ - { createdAt: "2024-01-15T10:15:00Z" }, - { createdAt: "2024-01-15T10:45:00Z" }, - ]; - - const result = groupChecksByTime(checksInSameHour, "day"); - - const expectedTime = new Date("2024-01-15T10:00:00Z").getTime(); - expect(Object.keys(result)).to.have.lengthOf(1); - expect(result[expectedTime].checks).to.have.lengthOf(2); - }); - }); - - describe("calculateGroupStats", function() { - // Mock getUptimePercentage function - let uptimePercentageStub; - - beforeEach(function() { - uptimePercentageStub = sinon.stub(); - uptimePercentageStub.returns(95); // Default return value - }); - - it("should calculate stats correctly for a group of checks", function() { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // (100 + 200 + 300) / 3 - }); - }); - - it("should handle empty checks array", function() { - const mockGroup = { - time: "2024-01-15", - checks: [], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 0, - totalIncidents: 0, - avgResponseTime: 0, - }); - }); - - it("should handle missing responseTime values", function() { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true }, - { status: false, responseTime: 200 }, - { status: true, responseTime: undefined }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: (2 / 3) * 100, - totalChecks: 3, - totalIncidents: 1, - avgResponseTime: 200, // 200 / 1 - }); - }); - - it("should handle all checks with status false", function() { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: false, responseTime: 100 }, - { status: false, responseTime: 200 }, - { status: false, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 0, - totalChecks: 3, - totalIncidents: 3, - avgResponseTime: 200, - }); - }); - - it("should handle all checks with status true", function() { - const mockGroup = { - time: "2024-01-15", - checks: [ - { status: true, responseTime: 100 }, - { status: true, responseTime: 200 }, - { status: true, responseTime: 300 }, - ], - }; - - const result = calculateGroupStats(mockGroup, uptimePercentageStub); - - expect(result).to.deep.equal({ - time: "2024-01-15", - uptimePercentage: 100, - totalChecks: 3, - totalIncidents: 0, - avgResponseTime: 200, - }); - }); - }); - - describe("getMonitorStatsById", function() { - const now = new Date(); - const oneHourAgo = new Date(now - 3600000); - const twoHoursAgo = new Date(now - 7200000); - - const mockMonitor = { - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const mockMonitorPing = { - _id: "monitor123", - type: "ping", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - const mockMonitorDocker = { - _id: "monitor123", - type: "docker", - name: "Test Monitor", - url: "https://test.com", - toObject: () => ({ - _id: "monitor123", - type: "http", - name: "Test Monitor", - url: "https://test.com", - }), - }; - - const checkDocs = [ - { - monitorId: "monitor123", - status: true, - responseTime: 100, - createdAt: new Date("2024-01-01T12:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: true, - responseTime: 150, - createdAt: new Date("2024-01-01T11:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - { - monitorId: "monitor123", - status: false, - responseTime: 200, - createdAt: new Date("2024-01-01T10:00:00Z"), - toObject: function () { - return { - monitorId: this.monitorId, - status: this.status, - responseTime: this.responseTime, - createdAt: this.createdAt, - }; - }, - }, - ]; - const req = { - params: { monitorId: "monitor123" }, - query: { - dateRange: "day", - sortOrder: "desc", - numToDisplay: 10, - normalize: true, - }, - }; - - beforeEach(function() { - checkFindStub.returns({ - sort: () => checkDocs, - }); - monitorFindByIdStub.returns(mockMonitor); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return monitor stats with calculated values, sort order desc", async function() { - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, ping type", async function() { - monitorFindByIdStub.returns(mockMonitorPing); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values, docker type", async function() { - monitorFindByIdStub.returns(mockMonitorDocker); - req.query.sortOrder = "desc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should return monitor stats with calculated values", async function() { - req.query.sortOrder = "asc"; - const result = await getMonitorStatsById(req); - expect(result).to.include.keys([ - "_id", - "type", - "name", - "url", - "uptimeDuration", - "lastChecked", - "latestResponseTime", - "periodIncidents", - "periodTotalChecks", - "periodAvgResponseTime", - "periodUptime", - "aggregateData", - ]); - expect(result.latestResponseTime).to.equal(100); - expect(result.periodTotalChecks).to.equal(3); - expect(result.periodIncidents).to.equal(1); - expect(result.periodUptime).to.be.a("number"); - expect(result.aggregateData).to.be.an("array"); - }); - - it("should throw error when monitor is not found", async function() { - monitorFindByIdStub.returns(Promise.resolve(null)); - - const req = { - params: { monitorId: "nonexistent" }, - }; - - try { - await getMonitorStatsById(req); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error).to.be.an("Error"); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorStatsById"); - } - }); - }); - - describe("getMonitorById", function() { - let notificationFindStub; - let monitorSaveStub; - - beforeEach(function() { - // Create stubs - notificationFindStub = sinon.stub(Notification, "find"); - monitorSaveStub = sinon.stub().resolves(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return monitor with notifications when found", async function() { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const mockNotifications = [ - { _id: "notif1", message: "Test notification 1" }, - { _id: "notif2", message: "Test notification 2" }, - ]; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - const result = await getMonitorById(monitorId); - expect(result._id).to.equal(monitorId); - expect(result.name).to.equal("Test Monitor"); - expect(monitorFindByIdStub.calledWith(monitorId)).to.be.true; - expect(notificationFindStub.calledWith({ monitorId })).to.be.true; - expect(monitorSaveStub.calledOnce).to.be.true; - }); - - it("should throw 404 error when monitor not found", async function() { - // Arrange - const monitorId = "nonexistent"; - monitorFindByIdStub.resolves(null); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(error.status).to.equal(404); - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - } - }); - - it("should handle database errors properly", async function() { - // Arrange - const monitorId = "123"; - const dbError = new Error("Database connection failed"); - monitorFindByIdStub.rejects(dbError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Database connection failed"); - } - }); - - it("should handle notification fetch errors", async function() { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: monitorSaveStub, - }; - const notificationError = new Error("Notification fetch failed"); - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.rejects(notificationError); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Notification fetch failed"); - } - }); - - it("should handle monitor save errors", async function() { - // Arrange - const monitorId = "123"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - save: sinon.stub().rejects(new Error("Save failed")), - }; - const mockNotifications = []; - - monitorFindByIdStub.resolves(mockMonitor); - notificationFindStub.resolves(mockNotifications); - - // Act & Assert - try { - await getMonitorById(monitorId); - expect.fail("Should have thrown an error"); - } catch (error) { - expect(error.service).to.equal("monitorModule"); - expect(error.method).to.equal("getMonitorById"); - expect(error.message).to.equal("Save failed"); - } - }); - }); - - describe("getMonitorsAndSummaryByTeamId", function() { - it("should return monitors and correct summary counts", async function() { - // Arrange - const teamId = "team123"; - const type = "http"; - const mockMonitors = [ - { teamId, type, status: true, isActive: true }, // up - { teamId, type, status: false, isActive: true }, // down - { teamId, type, status: null, isActive: false }, // paused - { teamId, type, status: true, isActive: true }, // up - ]; - monitorFindStub.resolves(mockMonitors); - - // Act - const result = await getMonitorsAndSummaryByTeamId(teamId, type); - - // Assert - expect(result.monitors).to.have.lengthOf(4); - expect(result.monitorCounts).to.deep.equal({ - up: 2, - down: 1, - paused: 1, - total: 4, - }); - expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true; - }); - - it("should return empty results for non-existent team", async function() { - // Arrange - monitorFindStub.resolves([]); - - // Act - const result = await getMonitorsAndSummaryByTeamId("nonexistent", "http"); - - // Assert - expect(result.monitors).to.have.lengthOf(0); - expect(result.monitorCounts).to.deep.equal({ - up: 0, - down: 0, - paused: 0, - total: 0, - }); - }); - - it("should handle database errors", async function() { - // Arrange - const error = new Error("Database error"); - error.service = "MonitorModule"; - error.method = "getMonitorsAndSummaryByTeamId"; - monitorFindStub.rejects(error); - - // Act & Assert - try { - await getMonitorsAndSummaryByTeamId("team123", "http"); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err).to.equal(error); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsAndSummaryByTeamId"); - } - }); - }); - - describe("getMonitorsByTeamId", function() { - beforeEach(function() { - // Chain stubs for Monitor.find().skip().limit().sort() - - // Stub for CHECK_MODEL_LOOKUP model find - checkFindStub.returns({ - sort: sinon.stub().returns({ - limit: sinon.stub().returns([]), - }), - }); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should return monitors with basic query parameters", async function() { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: false, - checkOrder: "desc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should return monitors with basic query parameters", async function() { - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const req = { - params: { teamId: "team123" }, - query: { - type: "http", - page: 0, - rowsPerPage: 10, - field: "name", - status: true, - checkOrder: "asc", - }, - }; - - monitorCountStub.resolves(2); - - const result = await getMonitorsByTeamId(req); - - expect(result).to.have.property("monitors"); - expect(result).to.have.property("monitorCount", 2); - }); - - it("should handle type filter with array input", async function() { - const req = { - params: { teamId: "team123" }, - query: { - type: ["http", "ping"], - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - type: { $in: ["http", "ping"] }, - }); - }); - - it("should handle text search filter", async function() { - const req = { - params: { teamId: "team123" }, - query: { - filter: "search", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - expect(Monitor.find.firstCall.args[0]).to.deep.equal({ - teamId: "team123", - $or: [ - { name: { $regex: "search", $options: "i" } }, - { url: { $regex: "search", $options: "i" } }, - ], - }); - }); - - it("should handle pagination parameters", async function() { - const req = { - params: { teamId: "team123" }, - query: { - page: 2, - rowsPerPage: 5, - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should handle sorting parameters", async function() { - const req = { - params: { teamId: "team123" }, - query: { - field: "name", - order: "asc", - }, - }; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns([]), - }), - }), - }); - monitorCountStub.resolves(0); - - await getMonitorsByTeamId(req); - - const result = await getMonitorsByTeamId(req); - expect(result).to.deep.equal({ - monitors: [], - monitorCount: 0, - }); - }); - - it("should return early when limit is -1", async function() { - // Arrange - const req = { - params: { teamId: "team123" }, - query: { - limit: "-1", - }, - }; - - const mockMonitors = [ - { _id: "1", type: "http" }, - { _id: "2", type: "ping" }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - monitorCountStub.resolves(2); - - // Act - const result = await getMonitorsByTeamId(req); - - // Assert - expect(result).to.deep.equal({ - monitors: mockMonitors, - monitorCount: 2, - }); - }); - - it("should normalize checks when normalize parameter is provided", async function() { - const req = { - params: { teamId: "team123" }, - query: { normalize: "true" }, - }; - monitorCountStub.resolves(2); - - const mockMonitors = [ - { _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) }, - { _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) }, - ]; - - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().returns(mockMonitors), - }), - }), - }); - - const result = await getMonitorsByTeamId(req); - expect(result.monitorCount).to.equal(2); - expect(result.monitors).to.have.lengthOf(2); - }); - - it("should handle database errors", async function() { - const req = { - params: { teamId: "team123" }, - query: {}, - }; - - const error = new Error("Database error"); - monitorFindStub.returns({ - skip: sinon.stub().returns({ - limit: sinon.stub().returns({ - sort: sinon.stub().throws(error), - }), - }), - }); - - try { - await getMonitorsByTeamId(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("getMonitorsByTeamId"); - expect(err.message).to.equal("Database error"); - } - }); - }); - - describe("createMonitor", function() { - it("should create a monitor without notifications", async function() { - let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves(); - - const req = { - body: { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: ["someNotification"], - }, - }; - - const expectedMonitor = { - name: "Test Monitor", - url: "http://test.com", - type: "http", - notifications: undefined, - save: monitorSaveStub, - }; - - const result = await createMonitor(req); - expect(result.name).to.equal(expectedMonitor.name); - expect(result.url).to.equal(expectedMonitor.url); - }); - - it("should handle database errors", async function() { - const req = { - body: { - name: "Test Monitor", - }, - }; - - try { - await createMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("createMonitor"); - } - }); - }); - - describe("deleteMonitor", function() { - it("should delete a monitor successfully", async function() { - const monitorId = "123456789"; - const mockMonitor = { - _id: monitorId, - name: "Test Monitor", - url: "http://test.com", - }; - - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(mockMonitor); - - const result = await deleteMonitor(req); - - expect(result).to.deep.equal(mockMonitor); - sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId); - }); - - it("should throw error when monitor not found", async function() { - const monitorId = "nonexistent123"; - const req = { - params: { monitorId }, - }; - - monitorFindByIdAndDeleteStub.resolves(null); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - - it("should handle database errors", async function() { - const monitorId = "123456789"; - const req = { - params: { monitorId }, - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndDeleteStub.rejects(dbError); - - try { - await deleteMonitor(req); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitor"); - } - }); - }); - - describe("deleteAllMonitors", function() { - it("should delete all monitors for a team successfully", async function() { - const teamId = "team123"; - const mockMonitors = [ - { _id: "1", name: "Monitor 1", teamId }, - { _id: "2", name: "Monitor 2", teamId }, - ]; - - monitorFindStub.resolves(mockMonitors); - monitorDeleteManyStub.resolves({ deletedCount: 2 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: mockMonitors, - deletedCount: 2, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should return empty array when no monitors found", async function() { - const teamId = "emptyTeam"; - - monitorFindStub.resolves([]); - monitorDeleteManyStub.resolves({ deletedCount: 0 }); - - const result = await deleteAllMonitors(teamId); - - expect(result).to.deep.equal({ - monitors: [], - deletedCount: 0, - }); - sinon.assert.calledWith(monitorFindStub, { teamId }); - sinon.assert.calledWith(monitorDeleteManyStub, { teamId }); - }); - - it("should handle database errors", async function() { - const teamId = "team123"; - const dbError = new Error("Database connection error"); - monitorFindStub.rejects(dbError); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - - it("should handle deleteMany errors", async function() { - const teamId = "team123"; - monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]); - monitorDeleteManyStub.rejects(new Error("Delete operation failed")); - - try { - await deleteAllMonitors(teamId); - } catch (err) { - expect(err.message).to.equal("Delete operation failed"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteAllMonitors"); - } - }); - }); - - describe("deleteMonitorsByUserId", function() { - beforeEach(function() {}); - - afterEach(function() { - sinon.restore(); - }); - - it("should delete all monitors for a user successfully", async function() { - // Arrange - const userId = "user123"; - const mockResult = { - deletedCount: 3, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result).to.deep.equal(mockResult); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should return zero deletedCount when no monitors found", async function() { - // Arrange - const userId = "nonexistentUser"; - const mockResult = { - deletedCount: 0, - acknowledged: true, - }; - - monitorDeleteManyStub.resolves(mockResult); - - // Act - const result = await deleteMonitorsByUserId(userId); - - // Assert - expect(result.deletedCount).to.equal(0); - sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId }); - }); - - it("should handle database errors", async function() { - // Arrange - const userId = "user123"; - const dbError = new Error("Database connection error"); - monitorDeleteManyStub.rejects(dbError); - - // Act & Assert - try { - await deleteMonitorsByUserId(userId); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("deleteMonitorsByUserId"); - } - }); - }); - - describe("editMonitor", function() { - it("should edit a monitor successfully", async function() { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: ["someNotification"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - url: "http://updated.com", - type: "http", - notifications: undefined, - }; - - const mockUpdatedMonitor = { - _id: candidateId, - ...expectedUpdateData, - }; - - monitorFindByIdAndUpdateStub.resolves(mockUpdatedMonitor); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.deep.equal(mockUpdatedMonitor); - sinon.assert.calledWith( - monitorFindByIdAndUpdateStub, - candidateId, - expectedUpdateData, - { - new: true, - } - ); - }); - - it("should return null when monitor not found", async function() { - // Arrange - const candidateId = "nonexistent123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - monitorFindByIdAndUpdateStub.resolves(null); - - // Act - const result = await editMonitor(candidateId, candidateMonitor); - - // Assert - expect(result).to.be.null; - sinon.assert.calledWith( - monitorFindByIdAndUpdateStub, - candidateId, - { name: "Updated Monitor", notifications: undefined }, - { new: true } - ); - }); - - it("should remove notifications from update data", async function() { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - notifications: ["notification1", "notification2"], - }; - - const expectedUpdateData = { - name: "Updated Monitor", - notifications: undefined, - }; - - monitorFindByIdAndUpdateStub.resolves({ - _id: candidateId, - ...expectedUpdateData, - }); - - // Act - await editMonitor(candidateId, candidateMonitor); - - // Assert - sinon.assert.calledWith( - monitorFindByIdAndUpdateStub, - candidateId, - expectedUpdateData, - { - new: true, - } - ); - }); - - it("should handle database errors", async function() { - // Arrange - const candidateId = "monitor123"; - const candidateMonitor = { - name: "Updated Monitor", - }; - - const dbError = new Error("Database connection error"); - monitorFindByIdAndUpdateStub.rejects(dbError); - - // Act & Assert - try { - await editMonitor(candidateId, candidateMonitor); - expect.fail("Should have thrown an error"); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("editMonitor"); - } - }); - }); - - describe("addDemoMonitors", function() { - it("should add demo monitors successfully", async function() { - // Arrange - const userId = "user123"; - const teamId = "team123"; - monitorInsertManyStub.resolves([{ _id: "123" }]); - const result = await addDemoMonitors(userId, teamId); - expect(result).to.deep.equal([{ _id: "123" }]); - }); - - it("should handle database errors", async function() { - const userId = "user123"; - const teamId = "team123"; - - const dbError = new Error("Database connection error"); - monitorInsertManyStub.rejects(dbError); - - try { - await addDemoMonitors(userId, teamId); - } catch (err) { - expect(err.message).to.equal("Database connection error"); - expect(err.service).to.equal("monitorModule"); - expect(err.method).to.equal("addDemoMonitors"); - } - }); - }); -}); diff --git a/Server/tests/db/notificationModule.test.js b/Server/tests/db/notificationModule.test.js deleted file mode 100644 index 5c77018c2..000000000 --- a/Server/tests/db/notificationModule.test.js +++ /dev/null @@ -1,80 +0,0 @@ -import sinon from "sinon"; -import Notification from "../../db/models/Notification.js"; -import { - createNotification, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, -} from "../../db/mongo/modules/notificationModule.js"; - -describe("notificationModule", function() { - const mockNotification = { - monitorId: "123", - }; - const mockNotifications = [mockNotification]; - let notificationSaveStub, notificationFindStub, notificationDeleteManyStub; - - beforeEach(function() { - notificationSaveStub = sinon.stub(Notification.prototype, "save").resolves(); - notificationFindStub = sinon.stub(Notification, "find").resolves(); - notificationDeleteManyStub = sinon.stub(Notification, "deleteMany").resolves(); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createNotification", function() { - it("should create a new notification", async function() { - const notificationData = { _id: "123", name: "test" }; - notificationSaveStub.resolves(notificationData); - const res = await createNotification(notificationData); - expect(res).to.deep.equal(notificationData); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - notificationSaveStub.rejects(err); - try { - await createNotification(mockNotification); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getNotificationsByMonitorId", function() { - it("should return notifications by monitor ID", async function() { - notificationFindStub.resolves(mockNotifications); - const res = await getNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - notificationFindStub.rejects(err); - try { - await getNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteNotificationsByMonitorId", function() { - it("should delete notifications by monitor ID", async function() { - notificationDeleteManyStub.resolves({ deletedCount: mockNotifications.length }); - const res = await deleteNotificationsByMonitorId(mockNotification.monitorId); - expect(res).to.deep.equal(mockNotifications.length); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - notificationDeleteManyStub.rejects(err); - try { - await deleteNotificationsByMonitorId(mockNotification.monitorId); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/db/pageSpeedCheckModule.test.js b/Server/tests/db/pageSpeedCheckModule.test.js deleted file mode 100644 index 3bacc4a3b..000000000 --- a/Server/tests/db/pageSpeedCheckModule.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import sinon from "sinon"; -import PageSpeedCheck from "../../db/models/PageSpeedCheck.js"; -import { - createPageSpeedCheck, - deletePageSpeedChecksByMonitorId, -} from "../../db/mongo/modules/pageSpeedCheckModule.js"; - -const mockPageSpeedCheck = { - monitorId: "monitorId", - bestPractices: 1, - seo: 1, - performance: 1, -}; - -const mockDeletedResult = { deletedCount: 1 }; - -describe("pageSpeedCheckModule", function() { - let pageSpeedCheckSaveStub, pageSpeedCheckDeleteManyStub; - - beforeEach(function() { - pageSpeedCheckSaveStub = sinon.stub(PageSpeedCheck.prototype, "save"); - pageSpeedCheckDeleteManyStub = sinon.stub(PageSpeedCheck, "deleteMany"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createPageSpeedCheck", function() { - it("should return a page speed check", async function() { - pageSpeedCheckSaveStub.resolves(mockPageSpeedCheck); - const pageSpeedCheck = await createPageSpeedCheck(mockPageSpeedCheck); - expect(pageSpeedCheck).to.deep.equal(mockPageSpeedCheck); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - pageSpeedCheckSaveStub.rejects(err); - try { - await expect(createPageSpeedCheck(mockPageSpeedCheck)); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deletePageSpeedChecksByMonitorId", function() { - it("should return the number of deleted checks", async function() { - pageSpeedCheckDeleteManyStub.resolves(mockDeletedResult); - const result = await deletePageSpeedChecksByMonitorId("monitorId"); - expect(result).to.deep.equal(mockDeletedResult.deletedCount); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - pageSpeedCheckDeleteManyStub.rejects(err); - try { - await expect(deletePageSpeedChecksByMonitorId("monitorId")); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/db/recoveryModule.test.js b/Server/tests/db/recoveryModule.test.js deleted file mode 100644 index 9d2978dd3..000000000 --- a/Server/tests/db/recoveryModule.test.js +++ /dev/null @@ -1,179 +0,0 @@ -import sinon from "sinon"; -import RecoveryToken from "../../db/models/RecoveryToken.js"; -import User from "../../db/models/User.js"; -import { - requestRecoveryToken, - validateRecoveryToken, - resetPassword, -} from "../../db/mongo/modules/recoveryModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockRecoveryToken = { - email: "test@test.com", - token: "1234567890", -}; - -const mockUser = { - email: "test@test.com", - password: "oldPassword", -}; - -const mockUserWithoutPassword = { - email: "test@test.com", -}; - -// Create a query builder that logs -const createQueryChain = (finalResult, comparePasswordResult = false) => ({ - select: () => ({ - select: async () => { - if (finalResult === mockUser) { - // Return a new object with all required methods - return { - email: "test@test.com", - password: "oldPassword", - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), - }; - } - return finalResult; - }, - }), - // Add methods to the top level too - comparePassword: sinon.stub().resolves(comparePasswordResult), - save: sinon.stub().resolves(), -}); - -describe("recoveryModule", function() { - let deleteManyStub, - saveStub, - findOneStub, - userCompareStub, - userSaveStub, - userFindOneStub; - let req, res; - - beforeEach(function() { - req = { - body: { email: "test@test.com" }, - }; - deleteManyStub = sinon.stub(RecoveryToken, "deleteMany"); - saveStub = sinon.stub(RecoveryToken.prototype, "save"); - findOneStub = sinon.stub(RecoveryToken, "findOne"); - userCompareStub = sinon.stub(User.prototype, "comparePassword"); - userSaveStub = sinon.stub(User.prototype, "save"); - userFindOneStub = sinon.stub().resolves(); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("requestRecoveryToken", function() { - it("should return a recovery token", async function() { - deleteManyStub.resolves(); - saveStub.resolves(mockRecoveryToken); - const result = await requestRecoveryToken(req, res); - expect(result.email).to.equal(mockRecoveryToken.email); - }); - - it("should handle an error", async function() { - const err = new Error("Test error"); - deleteManyStub.rejects(err); - try { - await requestRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("validateRecoveryToken", function() { - it("should return a recovery token if found", async function() { - findOneStub.resolves(mockRecoveryToken); - const result = await validateRecoveryToken(req, res); - expect(result).to.deep.equal(mockRecoveryToken); - }); - - it("should thrown an error if a token is not found", async function() { - findOneStub.resolves(null); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should handle DB errors", async function() { - const err = new Error("Test error"); - findOneStub.rejects(err); - try { - await validateRecoveryToken(req, res); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("resetPassword", function() { - beforeEach(function() { - req.body = { - password: "test", - newPassword: "test1", - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should thrown an error if a recovery token is not found", async function() { - findOneStub.resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND); - } - }); - - it("should throw an error if a user is not found", async function() { - findOneStub.resolves(mockRecoveryToken); - userFindOneStub = sinon.stub(User, "findOne").resolves(null); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should throw an error if the passwords match", async function() { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon - .stub(User, "findOne") - .returns(createQueryChain(mockUser, true)); - try { - await resetPassword(req, res); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); - } - }); - - it("should return a user without password if successful", async function() { - findOneStub.resolves(mockRecoveryToken); - saveStub.resolves(); - userFindOneStub = sinon - .stub(User, "findOne") - .returns(createQueryChain(mockUser)) // First call will resolve to mockUser - .onSecondCall() - .returns(createQueryChain(mockUserWithoutPassword)); - const result = await resetPassword(req, res); - expect(result).to.deep.equal(mockUserWithoutPassword); - }); - }); -}); diff --git a/Server/tests/db/settingsModule.test.js b/Server/tests/db/settingsModule.test.js deleted file mode 100644 index b518ba61d..000000000 --- a/Server/tests/db/settingsModule.test.js +++ /dev/null @@ -1,59 +0,0 @@ -import sinon from "sinon"; -import { - getAppSettings, - updateAppSettings, -} from "../../db/mongo/modules/settingsModule.js"; -import AppSettings from "../../db/models/AppSettings.js"; - -const mockAppSettings = { - appName: "Test App", -}; - -describe("SettingsModule", function() { - let appSettingsFindOneStub, appSettingsFindOneAndUpdateStub; - - beforeEach(function() { - appSettingsFindOneStub = sinon.stub(AppSettings, "findOne"); - appSettingsFindOneAndUpdateStub = sinon.stub(AppSettings, "findOneAndUpdate"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("getAppSettings", function() { - it("should return app settings", async function() { - appSettingsFindOneStub.resolves(mockAppSettings); - const result = await getAppSettings(); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function() { - const err = new Error("Test error"); - appSettingsFindOneStub.throws(err); - try { - await getAppSettings(); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); - - describe("updateAppSettings", function() { - it("should update app settings", async function() { - appSettingsFindOneAndUpdateStub.resolves(mockAppSettings); - const result = await updateAppSettings(mockAppSettings); - expect(result).to.deep.equal(mockAppSettings); - }); - - it("should handle an error", async function() { - const err = new Error("Test error"); - appSettingsFindOneAndUpdateStub.throws(err); - try { - await updateAppSettings(mockAppSettings); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/db/statusPageModule.test.js b/Server/tests/db/statusPageModule.test.js deleted file mode 100644 index 3d5d5e9d0..000000000 --- a/Server/tests/db/statusPageModule.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import sinon from "sinon"; -import { - createStatusPage, - getStatusPageByUrl, -} from "../../db/mongo/modules/statusPageModule.js"; -import StatusPage from "../../db/models/StatusPage.js"; -import { errorMessages } from "../../utils/messages.js"; - -describe("statusPageModule", function() { - let statusPageFindOneStub, statusPageSaveStub, statusPageFindStub; - - beforeEach(function() { - statusPageSaveStub = sinon.stub(StatusPage.prototype, "save"); - statusPageFindOneStub = sinon.stub(StatusPage, "findOne"); - statusPageFindStub = sinon.stub(StatusPage, "find"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createStatusPage", function() { - it("should throw an error if a non-unique url is provided", async function() { - statusPageFindOneStub.resolves(true); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error.status).to.equal(400); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_URL_NOT_UNIQUE); - } - }); - - it("should handle duplicate URL errors", async function() { - const err = new Error("test"); - err.code = 11000; - statusPageSaveStub.rejects(err); - try { - await createStatusPage({ url: "test" }); - } catch (error) { - expect(error).to.deep.equal(err); - } - }); - - it("should return a status page if a unique url is provided", async function() { - statusPageFindOneStub.resolves(null); - statusPageFindStub.resolves([]); - const mockStatusPage = { url: "test" }; - const statusPage = await createStatusPage(mockStatusPage); - expect(statusPage).to.exist; - expect(statusPage.url).to.equal(mockStatusPage.url); - }); - }); - - describe("getStatusPageByUrl", function() { - it("should throw an error if a status page is not found", async function() { - statusPageFindOneStub.resolves(null); - try { - await getStatusPageByUrl("test"); - } catch (error) { - expect(error.status).to.equal(404); - expect(error.message).to.equal(errorMessages.STATUS_PAGE_NOT_FOUND); - } - }); - - it("should return a status page if a status page is found", async function() { - const mockStatusPage = { url: "test" }; - statusPageFindOneStub.resolves(mockStatusPage); - const statusPage = await getStatusPageByUrl(mockStatusPage.url); - expect(statusPage).to.exist; - expect(statusPage).to.deep.equal(mockStatusPage); - }); - }); -}); diff --git a/Server/tests/db/userModule.test.js b/Server/tests/db/userModule.test.js deleted file mode 100644 index b52ba1880..000000000 --- a/Server/tests/db/userModule.test.js +++ /dev/null @@ -1,304 +0,0 @@ -import sinon from "sinon"; -import UserModel from "../../db/models/User.js"; -import TeamModel from "../../db/models/Team.js"; -import { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, -} from "../../db/mongo/modules/userModule.js"; -import { errorMessages } from "../../utils/messages.js"; - -const mockUser = { - email: "test@test.com", - password: "password", - role: ["user"], -}; -const mockSuperUser = { - email: "test@test.com", - password: "password", - role: ["superadmin"], -}; -const imageFile = { - image: 1, -}; - -describe("userModule", function() { - let teamSaveStub, - teamFindByIdAndDeleteStub, - userSaveStub, - userFindStub, - userFindOneStub, - userFindByIdAndUpdateStub, - userFindByIdAndDeleteStub, - userDeleteManyStub, - userUpdateOneStub, - generateAvatarImageStub, - parseBooleanStub; - - beforeEach(function() { - teamSaveStub = sinon.stub(TeamModel.prototype, "save"); - teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete"); - userSaveStub = sinon.stub(UserModel.prototype, "save"); - userFindStub = sinon.stub(UserModel, "find"); - userFindOneStub = sinon.stub(UserModel, "findOne"); - userFindByIdAndUpdateStub = sinon.stub(UserModel, "findByIdAndUpdate"); - userFindByIdAndDeleteStub = sinon.stub(UserModel, "findByIdAndDelete"); - userDeleteManyStub = sinon.stub(UserModel, "deleteMany"); - userUpdateOneStub = sinon.stub(UserModel, "updateOne"); - generateAvatarImageStub = sinon.stub().resolves({ image: 2 }); - parseBooleanStub = sinon.stub().returns(true); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("insertUser", function() { - it("should insert a regular user", async function() { - userSaveStub.resolves(mockUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await insertUser(mockUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockUser); - }); - - it("should insert a superadmin user", async function() { - userSaveStub.resolves(mockSuperUser); - userFindOneStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockSuperUser), - }), - }); - const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub); - expect(result).to.deep.equal(mockSuperUser); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - - it("should handle a duplicate key error", async function() { - const err = new Error("test error"); - err.code = 11000; - userSaveStub.rejects(err); - try { - await insertUser(mockUser, imageFile, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getUserByEmail", function() { - it("should return a user", async function() { - userFindOneStub.returns({ - select: sinon.stub().resolves(mockUser), - }); - const result = await getUserByEmail(mockUser.email); - expect(result).to.deep.equal(mockUser); - }); - }); - - describe("getUserByEmail", function() { - it("throw an error if a user is not found", async function() { - userFindOneStub.returns({ - select: sinon.stub().resolves(null), - }); - try { - await getUserByEmail(mockUser.email); - } catch (error) { - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - }); - - describe("updateUser", function() { - let req, res; - - beforeEach(function() { - req = { - params: { - userId: "testId", - }, - body: { - deleteProfileImage: "false", - email: "test@test.com", - }, - file: { - buffer: "test", - mimetype: "test", - }, - }; - res = {}; - }); - - afterEach(function() {}); - - it("should update a user", async function() { - parseBooleanStub.returns(false); - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser( - req, - res, - parseBooleanStub, - generateAvatarImageStub - ); - expect(result).to.deep.equal(mockUser); - }); - - it("should delete a user profile image", async function() { - req.body.deleteProfileImage = "true"; - userFindByIdAndUpdateStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves(mockUser), - }), - }); - const result = await updateUser( - req, - res, - parseBooleanStub, - generateAvatarImageStub - ); - expect(result).to.deep.equal(mockUser); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userFindByIdAndUpdateStub.throws(err); - try { - await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteUser", function() { - it("should return a deleted user", async function() { - userFindByIdAndDeleteStub.resolves(mockUser); - const result = await deleteUser("testId"); - expect(result).to.deep.equal(mockUser); - }); - - it("should throw an error if a user is not found", async function() { - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); - } - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userFindByIdAndDeleteStub.throws(err); - try { - await deleteUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteTeam", function() { - it("should return true if team deleted", async function() { - teamFindByIdAndDeleteStub.resolves(); - const result = await deleteTeam("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - teamFindByIdAndDeleteStub.throws(err); - try { - await deleteTeam("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("deleteAllOtherUsers", function() { - it("should return true if all other users deleted", async function() { - userDeleteManyStub.resolves(true); - const result = await deleteAllOtherUsers(); - expect(result).to.equal(true); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userDeleteManyStub.throws(err); - try { - await deleteAllOtherUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("getAllUsers", function() { - it("should return all users", async function() { - userFindStub.returns({ - select: sinon.stub().returns({ - select: sinon.stub().resolves([mockUser]), - }), - }); - const result = await getAllUsers(); - expect(result).to.deep.equal([mockUser]); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userFindStub.throws(err); - try { - await getAllUsers(); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); - - describe("logoutUser", function() { - it("should return true if user logged out", async function() { - userUpdateOneStub.resolves(true); - const result = await logoutUser("testId"); - expect(result).to.equal(true); - }); - - it("should handle an error", async function() { - const err = new Error("test error"); - userUpdateOneStub.throws(err); - try { - await logoutUser("testId"); - } catch (error) { - expect(error).to.exist; - expect(error).to.deep.equal(err); - } - }); - }); -}); diff --git a/Server/tests/services/emailService.test.js b/Server/tests/services/emailService.test.js deleted file mode 100644 index 2584aa1bc..000000000 --- a/Server/tests/services/emailService.test.js +++ /dev/null @@ -1,212 +0,0 @@ -import sinon from "sinon"; -import EmailService from "../../service/emailService.js"; - -describe("EmailService - Constructor", function() { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - - beforeEach(function() { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should initialize template loaders and email transporter", function() { - const emailService = new EmailService( - settingsServiceMock, - fsMock, - pathMock, - compileMock, - mjml2htmlMock, - nodemailerMock, - loggerMock - ); - - // Verify that the settingsService is assigned correctly - expect(emailService.settingsService).to.equal(settingsServiceMock); - - // Verify that the template loaders are initialized - expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function"); - expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function"); - - // Verify that the email transporter is initialized - expect(nodemailerMock.createTransport.calledOnce).to.be.true; - const emailConfig = nodemailerMock.createTransport.getCall(0).args[0]; - expect(emailConfig).to.deep.equal({ - host: "smtp.example.com", - port: 465, - secure: true, - auth: { - user: "test@example.com", - pass: "password", - }, - }); - }); - - it("should have undefined templates if FS fails", function() { - fsMock = { - readFileSync: sinon.stub().throws(new Error("File read error")), - }; - const emailService = new EmailService( - settingsServiceMock, - fsMock, - pathMock, - compileMock, - mjml2htmlMock, - nodemailerMock, - loggerMock - ); - expect(loggerMock.error.called).to.be.true; - expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error"); - }); -}); - -describe("EmailService - buildAndSendEmail", function() { - let settingsServiceMock; - let fsMock; - let pathMock; - let compileMock; - let mjml2htmlMock; - let nodemailerMock; - let loggerMock; - let emailService; - - beforeEach(function() { - settingsServiceMock = { - getSettings: sinon.stub().returns({ - systemEmailHost: "smtp.example.com", - systemEmailPort: 465, - systemEmailAddress: "test@example.com", - systemEmailPassword: "password", - }), - }; - - fsMock = { - readFileSync: sinon.stub().returns(""), - }; - - pathMock = { - join: sinon.stub().callsFake((...args) => args.join("/")), - }; - - compileMock = sinon.stub().returns(() => ""); - - mjml2htmlMock = sinon.stub().returns({ html: "" }); - - nodemailerMock = { - createTransport: sinon.stub().returns({ - sendMail: sinon.stub().resolves({ messageId: "12345" }), - }), - }; - - loggerMock = { - error: sinon.stub(), - }; - - emailService = new EmailService( - settingsServiceMock, - fsMock, - pathMock, - compileMock, - mjml2htmlMock, - nodemailerMock, - loggerMock - ); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should build and send email successfully", async function() { - const messageId = await emailService.buildAndSendEmail( - "welcomeEmailTemplate", - {}, - "recipient@example.com", - "Welcome" - ); - - expect(messageId).to.equal("12345"); - expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true; - }); - - it("should log error if building HTML fails", async function() { - mjml2htmlMock.throws(new Error("MJML error")); - - const messageId = await emailService.buildAndSendEmail( - "welcomeEmailTemplate", - {}, - "recipient@example.com", - "Welcome" - ); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - }); - - it("should log error if sending email fails", async function() { - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - await emailService.buildAndSendEmail( - "welcomeEmailTemplate", - {}, - "recipient@example.com", - "Welcome" - ); - expect(loggerMock.error.calledOnce).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error"); - }); - - it("should log error if both building HTML and sending email fail", async function() { - mjml2htmlMock.throws(new Error("MJML error")); - nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); - - const messageId = await emailService.buildAndSendEmail( - "welcomeEmailTemplate", - {}, - "recipient@example.com", - "Welcome" - ); - - expect(messageId).to.be.undefined; - expect(loggerMock.error.calledTwice).to.be.true; - expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error"); - expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error"); - }); - - it("should log an error if buildHtml fails", async function() {}); -}); diff --git a/Server/tests/services/jobQueue.test.js b/Server/tests/services/jobQueue.test.js deleted file mode 100644 index a7778a56d..000000000 --- a/Server/tests/services/jobQueue.test.js +++ /dev/null @@ -1,834 +0,0 @@ -import sinon from "sinon"; -import JobQueue from "../../service/jobQueue.js"; -import { log } from "console"; - -class QueueStub { - constructor(queueName, options) { - this.queueName = queueName; - this.options = options; - this.workers = []; - this.jobs = []; - } - - // Add any methods that are expected to be called on the Queue instance - add(job) { - this.jobs.push(job); - } - - removeRepeatable(id) { - const removedJob = this.jobs.find((job) => job.data._id === id); - this.jobs = this.jobs.filter((job) => job.data._id !== id); - if (removedJob) { - return true; - } - return false; - } - - getRepeatableJobs() { - return this.jobs; - } - async getJobs() { - return this.jobs; - } - - async pause() { - return true; - } - - async obliterate() { - return true; - } -} - -class WorkerStub { - constructor(QUEUE_NAME, workerTask) { - this.queueName = QUEUE_NAME; - this.workerTask = async () => workerTask({ data: { _id: 1 } }); - } - - async close() { - return true; - } -} - -describe("JobQueue", function() { - let settingsService, - logger, - db, - networkService, - statusService, - notificationService, - jobQueue; - - beforeEach(async function() { - settingsService = { getSettings: sinon.stub() }; - statusService = { updateStatus: sinon.stub() }; - notificationService = { handleNotifications: sinon.stub() }; - - logger = { error: sinon.stub(), info: sinon.stub() }; - db = { - getAllMonitors: sinon.stub().returns([]), - getMaintenanceWindowsByMonitorId: sinon.stub().returns([]), - }; - networkService = { getStatus: sinon.stub() }; - jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("createJobQueue", function() { - it("should create a new JobQueue and add jobs for active monitors", async function() { - db.getAllMonitors.returns([ - { id: 1, isActive: true }, - { id: 2, isActive: true }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - // There should be double the jobs, as one is meant to be instantly executed - // And one is meant to be enqueued - expect(jobQueue.queue.jobs.length).to.equal(4); - }); - - it("should reject with an error if an error occurs", async function() { - db.getAllMonitors.throws("Error"); - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createJobQueue"); - } - }); - - it("should reject with an error if an error occurs, should not overwrite error data", async function() { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - db.getAllMonitors.throws(error); - - try { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("Constructor", function() { - it("should construct a new JobQueue with default port and host if not provided", async function() { - settingsService.getSettings.returns({}); - - expect(jobQueue.connection.host).to.equal("127.0.0.1"); - expect(jobQueue.connection.port).to.equal(6379); - }); - - it("should construct a new JobQueue with provided port and host", async function() { - settingsService.getSettings.returns({ redisHost: "localhost", redisPort: 1234 }); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.connection.host).to.equal("localhost"); - expect(jobQueue.connection.port).to.equal(1234); - }); - }); - - describe("isMaintenanceWindow", function() { - it("should throw an error if error occurs", async function() { - db.getMaintenanceWindowsByMonitorId.throws("Error"); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.isInMaintenanceWindow(1); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("createWorker"); - } - }); - - it("should return true if in maintenance window with no repeat", async function() { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 1000).toISOString(), - end: new Date(Date.now() + 1000).toISOString(), - repeat: 0, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return true if in maintenance window with repeat", async function() { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 10000).toISOString(), - end: new Date(Date.now() - 5000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.true; - }); - - it("should return false if in end < start", async function() { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: true, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - - it("should return false if not in maintenance window", async function() { - db.getMaintenanceWindowsByMonitorId.returns([ - { - active: false, - start: new Date(Date.now() - 5000).toISOString(), - end: new Date(Date.now() - 10000).toISOString(), - repeat: 1000, - }, - ]); - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const inWindow = await jobQueue.isInMaintenanceWindow(1); - expect(inWindow).to.be.false; - }); - }); - - describe("createJobHandler", function() { - it("resolve to an error if an error is thrown within", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().throws("Error"); - try { - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.details).to.equal(`Error processing job 1: Error`); - } - }); - - it("should log info if job is in maintenance window", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(true); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(logger.info.calledOnce).to.be.true; - expect(logger.info.firstCall.args[0].message).to.equal( - "Monitor 1 is in maintenance window" - ); - }); - - it("should return if status has not changed", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon.stub().returns({ statusChanged: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should return if status has changed, but prevStatus was undefined (monitor paused)", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon - .stub() - .returns({ statusChanged: true, prevStatus: undefined }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true; - }); - - it("should call notification service if status changed and monitor was not paused", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.isInMaintenanceWindow = sinon.stub().returns(false); - statusService.updateStatus = sinon - .stub() - .returns({ statusChanged: true, prevStatus: false }); - const handler = jobQueue.createJobHandler(); - await handler({ data: { _id: 1 } }); - expect(jobQueue.notificationService.handleNotifications.calledOnce).to.be.true; - }); - }); - - describe("getWorkerStats", function() { - it("should throw an error if getRepeatable Jobs fails", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("Error"); - }; - try { - const stats = await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getWorkerStats"); - } - }); - - it("should throw an error if getRepeatable Jobs fails but respect existing error data", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.getWorkerStats(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("scaleWorkers", function() { - it("should scale workers to 5 if no workers", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers up", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - expect(jobQueue.workers.length).to.equal(20); - }); - - it("should scale workers down, even with error of worker.close fails", async function() { - WorkerStub.prototype.close = async () => { - throw new Error("Error"); - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 100, - jobs: Array.from({ length: 100 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should scale workers down", async function() { - WorkerStub.prototype.close = async () => { - return true; - }; - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - await jobQueue.scaleWorkers({ - load: 40, - jobs: Array.from({ length: 40 }, (_, i) => i + 1), - }); - - const res = await jobQueue.scaleWorkers({ - load: 0, - jobs: [], - }); - expect(jobQueue.workers.length).to.equal(5); - }); - - it("should return false if scaling doesn't happen", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const res = await jobQueue.scaleWorkers({ load: 5 }); - expect(jobQueue.workers.length).to.equal(5); - expect(res).to.be.false; - }); - }); - - describe("getJobs", function() { - it("should return jobs", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - const jobs = await jobQueue.getJobs(); - expect(jobs.length).to.equal(0); - }); - - it("should throw an error if getRepeatableJobs fails", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - throw new Error("error"); - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobs"); - } - }); - - it("should throw an error if getRepeatableJobs fails but respect existing error data", async function() { - const jobQueue = await JobQueue.createJobQueue( - db, - networkService, - statusService, - notificationService, - settingsService, - logger, - QueueStub, - WorkerStub - ); - try { - jobQueue.queue.getRepeatableJobs = async () => { - const error = new Error("Existing error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - await jobQueue.getJobs(true); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getJobStats", function() { - it("should return job stats for no jobs", async function() { - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ jobs: [], workers: 5 }); - }); - - it("should return job stats for jobs", async function() { - jobQueue.queue.getJobs = async () => { - return [{ data: { url: "test" }, getState: async () => "completed" }]; - }; - const jobStats = await jobQueue.getJobStats(); - expect(jobStats).to.deep.equal({ - jobs: [{ url: "test", state: "completed" }], - workers: 5, - }); - }); - - it("should reject with an error if mapping jobs fails", async function() { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - throw new Error("Mapping Error"); - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("getJobStats"); - } - }); - - it("should reject with an error if mapping jobs fails but respect existing error data", async function() { - jobQueue.queue.getJobs = async () => { - return [ - { - data: { url: "test" }, - getState: async () => { - const error = new Error("Mapping Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }, - }, - ]; - }; - try { - await jobQueue.getJobStats(); - } catch (error) { - expect(error.message).to.equal("Mapping Error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("addJob", function() { - it("should add a job to the queue", async function() { - jobQueue.addJob("test", { url: "test" }); - expect(jobQueue.queue.jobs.length).to.equal(1); - }); - - it("should reject with an error if adding fails", async function() { - jobQueue.queue.add = async () => { - throw new Error("Error adding job"); - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("addJob"); - } - }); - - it("should reject with an error if adding fails but respect existing error data", async function() { - jobQueue.queue.add = async () => { - const error = new Error("Error adding job"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - try { - await jobQueue.addJob("test", { url: "test" }); - } catch (error) { - expect(error.message).to.equal("Error adding job"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("deleteJob", function() { - it("should delete a job from the queue", async function() { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob(monitor); - // expect(jobQueue.queue.jobs.length).to.equal(0); - // expect(logger.info.calledOnce).to.be.true; - // expect(jobQueue.getWorkerStats.calledOnce).to.be.true; - // expect(jobQueue.scaleWorkers.calledOnce).to.be.true; - }); - - it("should log an error if job is not found", async function() { - jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] }); - jobQueue.scaleWorkers = sinon.stub(); - const monitor = { _id: 1 }; - const job = { data: monitor }; - jobQueue.queue.jobs = [job]; - await jobQueue.deleteJob({ id_: 2 }); - expect(logger.error.calledOnce).to.be.true; - }); - - it("should reject with an error if removeRepeatable fails", async function() { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("deleteJob"); - } - }); - - it("should reject with an error if removeRepeatable fails but respect existing error data", async function() { - jobQueue.queue.removeRepeatable = async () => { - const error = new Error("removeRepeatable error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.deleteJob({ _id: 1 }); - } catch (error) { - expect(error.message).to.equal("removeRepeatable error"); - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); - - describe("getMetrics", function() { - it("should return metrics for the job queue", async function() { - jobQueue.queue.getWaitingCount = async () => 1; - jobQueue.queue.getActiveCount = async () => 2; - jobQueue.queue.getCompletedCount = async () => 3; - jobQueue.queue.getFailedCount = async () => 4; - jobQueue.queue.getDelayedCount = async () => 5; - jobQueue.queue.getRepeatableJobs = async () => [1, 2, 3]; - const metrics = await jobQueue.getMetrics(); - expect(metrics).to.deep.equal({ - waiting: 1, - active: 2, - completed: 3, - failed: 4, - delayed: 5, - repeatableJobs: 3, - }); - }); - - it("should log an error if metrics operations fail", async function() { - jobQueue.queue.getWaitingCount = async () => { - throw new Error("Error"); - }; - await jobQueue.getMetrics(); - expect(logger.error.calledOnce).to.be.true; - expect(logger.error.firstCall.args[0].message).to.equal("Error"); - }); - }); - - describe("obliterate", function() { - it("should return true if obliteration is successful", async function() { - jobQueue.queue.pause = async () => true; - jobQueue.getJobs = async () => [{ key: 1, id: 1 }]; - jobQueue.queue.removeRepeatableByKey = async () => true; - jobQueue.queue.remove = async () => true; - jobQueue.queue.obliterate = async () => true; - const obliteration = await jobQueue.obliterate(); - expect(obliteration).to.be.true; - }); - - it("should throw an error if obliteration fails", async function() { - jobQueue.getMetrics = async () => { - throw new Error("Error"); - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("JobQueue"); - expect(error.method).to.equal("obliterate"); - } - }); - - it("should throw an error if obliteration fails but respect existing error data", async function() { - jobQueue.getMetrics = async () => { - const error = new Error("Error"); - error.service = "otherService"; - error.method = "otherMethod"; - throw error; - }; - - try { - await jobQueue.obliterate(); - } catch (error) { - expect(error.service).to.equal("otherService"); - expect(error.method).to.equal("otherMethod"); - } - }); - }); -}); diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js deleted file mode 100644 index a5a59c938..000000000 --- a/Server/tests/services/networkService.test.js +++ /dev/null @@ -1,455 +0,0 @@ -import sinon from "sinon"; -import NetworkService from "../../service/networkService.js"; -import { expect } from "chai"; -import http from "http"; -import { errorMessages } from "../../utils/messages.js"; -describe("Network Service", function() { - let axios, ping, Docker, logger, networkService; - - beforeEach(function() { - axios = { - get: sinon.stub().resolves({ - data: { foo: "bar" }, - status: 200, - }), - }; - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["http://test.com"], - Id: "http://test.com", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "running" } }), - }); - }; - ping = { - promise: { - probe: sinon - .stub() - .resolves({ response: { alive: true }, responseTime: 100, alive: true }), - }, - }; - logger = { error: sinon.stub() }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - }); - - describe("constructor", function() { - it("should create a new NetworkService instance", function() { - const networkService = new NetworkService(); - expect(networkService).to.be.an.instanceOf(NetworkService); - }); - }); - - describe("timeRequest", function() { - it("should time an asynchronous operation", async function() { - const operation = sinon.stub().resolves("success"); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.equal("success"); - expect(responseTime).to.be.a("number"); - }); - - it("should handle errors if operation throws error", async function() { - const error = new Error("Test error"); - const operation = sinon.stub().throws(error); - const { response, responseTime } = await networkService.timeRequest(operation); - expect(response).to.be.null; - expect(responseTime).to.be.a("number"); - expect(error.message).to.equal("Test error"); - }); - }); - - describe("requestPing", function() { - it("should return a response object if ping successful", async function() { - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.true; - }); - - it("should return a response object if ping unsuccessful", async function() { - const error = new Error("Test error"); - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const pingResult = await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - expect(pingResult.monitorId).to.equal("123"); - expect(pingResult.type).to.equal("ping"); - expect(pingResult.responseTime).to.be.a("number"); - expect(pingResult.status).to.be.false; - expect(pingResult.code).to.equal(networkService.PING_ERROR); - }); - - it("should throw an error if ping cannot resolve", async function() { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPing({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPing"); - } - }); - }); - - describe("requestHttp", function() { - it("should return a response object if http successful", async function() { - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if http unsuccessful", async function() { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if http unsuccessful with unknown code", async function() { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; - const httpResult = await networkService.requestHttp(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("http"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if an error occurs", async function() { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHttp({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHttp"); - } - }); - }); - - describe("requestPagespeed", function() { - it("should return a response object if pagespeed successful", async function() { - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.true; - }); - - it("should return a response object if pagespeed unsuccessful", async function() { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(404); - }); - - it("should return a response object if pagespeed unsuccessful with an unknown code", async function() { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; - const pagespeedResult = await networkService.requestPagespeed(job); - expect(pagespeedResult.monitorId).to.equal("123"); - expect(pagespeedResult.type).to.equal("pagespeed"); - expect(pagespeedResult.responseTime).to.be.a("number"); - expect(pagespeedResult.status).to.be.false; - expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if pagespeed cannot resolve", async function() { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestPagespeed({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestPagespeed"); - } - }); - }); - - describe("requestHardware", function() { - it("should return a response object if hardware successful", async function() { - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware successful and job has a secret", async function() { - const job = { - data: { - url: "http://test.com", - _id: "123", - type: "hardware", - secret: "my_secret", - }, - }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.true; - }); - - it("should return a response object if hardware unsuccessful", async function() { - const error = new Error("Test error"); - error.response = { status: 404 }; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(404); - }); - - it("should return a response object if hardware unsuccessful with unknown code", async function() { - const error = new Error("Test error"); - error.response = {}; - networkService.timeRequest = sinon - .stub() - .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; - const httpResult = await networkService.requestHardware(job); - expect(httpResult.monitorId).to.equal("123"); - expect(httpResult.type).to.equal("hardware"); - expect(httpResult.responseTime).to.be.a("number"); - expect(httpResult.status).to.be.false; - expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if hardware cannot resolve", async function() { - const error = new Error("test error"); - networkService.timeRequest = sinon.stub().throws(error); - try { - await networkService.requestHardware({ - data: { url: "http://test.com", _id: "123" }, - }); - } catch (error) { - expect(error).to.exist; - expect(error.method).to.equal("requestHardware"); - } - }); - }); - - describe("requestDocker", function() { - it("should return a response object if docker successful", async function() { - const job = { data: { url: "http://test.com", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.monitorId).to.equal("123"); - expect(dockerResult.type).to.equal("docker"); - expect(dockerResult.responseTime).to.be.a("number"); - expect(dockerResult.status).to.be.true; - }); - - it("should return a response object with status false if container not running", async function() { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().resolves({ State: { Status: "stopped" } }), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(200); - }); - - it("should handle an error when fetching the container", async function() { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().returns({ - inspect: sinon.stub().throws(new Error("test error")), - }); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - const dockerResult = await networkService.requestDocker(job); - expect(dockerResult.status).to.be.false; - expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR); - }); - - it("should throw an error if operations fail", async function() { - Docker = class { - listContainers = sinon.stub().resolves([ - { - Names: ["/my_container"], - Id: "abc123", - }, - ]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal("test error"); - } - }); - - it("should throw an error if no matching images found", async function() { - Docker = class { - listContainers = sinon.stub().resolves([]); - getContainer = sinon.stub().throws(new Error("test error")); - }; - networkService = new NetworkService(axios, ping, logger, http, Docker); - const job = { data: { url: "abc123", _id: "123", type: "docker" } }; - try { - await networkService.requestDocker(job); - } catch (error) { - expect(error.message).to.equal(errorMessages.DOCKER_NOT_FOUND); - } - }); - }); - - describe("getStatus", function() { - beforeEach(function() { - networkService.requestPing = sinon.stub(); - networkService.requestHttp = sinon.stub(); - networkService.requestPagespeed = sinon.stub(); - networkService.requestHardware = sinon.stub(); - networkService.requestDocker = sinon.stub(); - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should call requestPing if type is ping", async function() { - await networkService.getStatus({ data: { type: "ping" } }); - expect(networkService.requestPing.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestHttp if type is http", async function() { - await networkService.getStatus({ data: { type: "http" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.calledOnce).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestPagespeed if type is pagespeed", async function() { - await networkService.getStatus({ data: { type: "pagespeed" } }); - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; - expect(networkService.requestPagespeed.calledOnce).to.be.true; - }); - - it("should call requestHardware if type is hardware", async function() { - await networkService.getStatus({ data: { type: "hardware" } }); - expect(networkService.requestHardware.calledOnce).to.be.true; - expect(networkService.requestDocker.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should call requestDocker if type is Docker", async function() { - await networkService.getStatus({ data: { type: "docker" } }); - expect(networkService.requestDocker.calledOnce).to.be.true; - expect(networkService.requestHardware.notCalled).to.be.true; - expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestPagespeed.notCalled).to.be.true; - }); - - it("should throw an error if an unknown type is provided", async function() { - try { - await networkService.getStatus({ data: { type: "unknown" } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job type is undefined", async function() { - try { - await networkService.getStatus({ data: { type: undefined } }); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is empty", async function() { - try { - await networkService.getStatus({}); - } catch (error) { - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - - it("should throw an error if job is null", async function() { - try { - await networkService.getStatus(null); - } catch (error) { - expect(error.service).to.equal("NetworkService"); - expect(error.method).to.equal("getStatus"); - expect(error.message).to.equal("Unsupported type: unknown"); - } - }); - }); -}); diff --git a/Server/tests/services/notificationService.test.js b/Server/tests/services/notificationService.test.js deleted file mode 100644 index e6220b20c..000000000 --- a/Server/tests/services/notificationService.test.js +++ /dev/null @@ -1,302 +0,0 @@ -import sinon from "sinon"; -import NotificationService from "../../service/notificationService.js"; -import { expect } from "chai"; - -describe("NotificationService", function() { - let emailService, db, logger, notificationService; - - beforeEach(function() { - db = { - getNotificationsByMonitorId: sinon.stub(), - }; - emailService = { - buildAndSendEmail: sinon.stub(), - }; - logger = { - warn: sinon.stub(), - }; - - notificationService = new NotificationService(emailService, db, logger); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("constructor", function() { - it("should create a new instance of NotificationService", function() { - expect(notificationService).to.be.an.instanceOf(NotificationService); - }); - }); - - describe("sendEmail", function() { - it("should send an email notification with Up Template", async function() { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - notificationService.emailService.buildAndSendEmail.calledWith( - "serverIsUpTemplate", - { monitor: "Test Monitor", url: "http://test.com" } - ) - ); - }); - - it("should send an email notification with Down Template", async function() { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: false, - prevStatus: true, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - - it("should send an email notification with Up Template", async function() { - const networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - const address = "test@test.com"; - await notificationService.sendEmail(networkResponse, address); - expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true; - }); - }); - - describe("handleNotifications", function() { - it("should handle notifications based on the network response", async function() { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "email", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle hardware notifications", async function() { - notificationService.sendEmail = sinon.stub(); - const res = await notificationService.handleNotifications({ - monitor: { - type: "hardware", - address: "www.google.com", - }, - }); - expect(res).to.be.true; - }); - - it("should handle an error when getting notifications", async function() { - const testError = new Error("Test Error"); - notificationService.db.getNotificationsByMonitorId.rejects(testError); - await notificationService.handleNotifications({ monitorId: "123" }); - expect(notificationService.logger.warn.calledOnce).to.be.true; - }); - }); - - describe("sendHardwareEmail", function() { - let networkResponse, address, alerts; - - beforeEach(function() { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - status: true, - prevStatus: false, - }; - address = "test@test.com"; - alerts = ["test"]; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should send an email notification with Hardware Template", async function() { - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail( - networkResponse, - address, - alerts - ); - expect(res).to.be.true; - }); - - it("should return false if no alerts are provided", async function() { - alerts = []; - emailService.buildAndSendEmail.resolves(true); - const res = await notificationService.sendHardwareEmail( - networkResponse, - address, - alerts - ); - expect(res).to.be.false; - }); - }); - - describe("handleStatusNotifications", function() { - let networkResponse; - - beforeEach(function() { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - }, - statusChanged: true, - status: true, - prevStatus: false, - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - it("should handle status notifications", async function() { - db.getNotificationsByMonitorId.resolves([ - { type: "email", address: "test@test.com" }, - ]); - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return false if status hasn't changed", async function() { - networkResponse.statusChanged = false; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if prevStatus is undefined", async function() { - networkResponse.prevStatus = undefined; - const res = await notificationService.handleStatusNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should handle an error", async function() { - const testError = new Error("Test Error"); - db.getNotificationsByMonitorId.rejects(testError); - try { - await notificationService.handleStatusNotifications(networkResponse); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - expect(error.message).to.equal("Test Error"); - } - }); - }); - - describe("handleHardwareNotifications", function() { - let networkResponse; - - beforeEach(function() { - networkResponse = { - monitor: { - name: "Test Monitor", - url: "http://test.com", - thresholds: { - usage_cpu: 1, - usage_memory: 1, - usage_disk: 1, - }, - }, - payload: { - data: { - cpu: { - usage_percent: 0.655, - }, - memory: { - usage_percent: 0.783, - }, - disk: [ - { - name: "/dev/sda1", - usage_percent: 0.452, - }, - { - name: "/dev/sdb1", - usage_percent: 0.627, - }, - ], - }, - }, - }; - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("it should return false if no thresholds are set", function() { - it("should return false if no thresholds are set", async function() { - networkResponse.monitor.thresholds = undefined; - const res = - await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return false if metrics are null", async function() { - networkResponse.payload.data = null; - const res = - await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.false; - }); - - it("should return true if request is well formed and thresholds > 0", async function() { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - const res = - await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - - it("should return true if thresholds are exceeded", async function() { - db.getNotificationsByMonitorId.resolves([ - { - type: "email", - address: "test@test.com", - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - save: sinon.stub().resolves(), - }, - ]); - networkResponse.monitor.thresholds = { - usage_cpu: 0.01, - usage_memory: 0.01, - usage_disk: 0.01, - }; - const res = - await notificationService.handleHardwareNotifications(networkResponse); - expect(res).to.be.true; - }); - }); - }); -}); diff --git a/Server/tests/services/settingsService.test.js b/Server/tests/services/settingsService.test.js deleted file mode 100644 index 94f7737bc..000000000 --- a/Server/tests/services/settingsService.test.js +++ /dev/null @@ -1,142 +0,0 @@ -import sinon from "sinon"; -import SettingsService from "../../service/settingsService.js"; -import { expect } from "chai"; -import NetworkService from "../../service/networkService.js"; -const SERVICE_NAME = "SettingsService"; - -describe("SettingsService", function() { - let sandbox, mockAppSettings; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost"); - sandbox.stub(process.env, "JWT_SECRET").value("secret"); - sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret"); - sandbox.stub(process.env, "DB_TYPE").value("postgres"); - sandbox - .stub(process.env, "DB_CONNECTION_STRING") - .value("postgres://user:pass@localhost/db"); - sandbox.stub(process.env, "REDIS_HOST").value("localhost"); - sandbox.stub(process.env, "REDIS_PORT").value("6379"); - sandbox.stub(process.env, "TOKEN_TTL").value("3600"); - sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400"); - sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey"); - sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525"); - sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com"); - sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password"); - }); - - mockAppSettings = { - settingOne: 123, - settingTwo: 456, - }; - - afterEach(function () { - sandbox.restore(); - sinon.restore(); - }); - - describe("constructor", function() { - it("should construct a new SettingsService", function() { - const settingsService = new SettingsService(mockAppSettings); - expect(settingsService.appSettings).to.equal(mockAppSettings); - }); - }); - - describe("loadSettings", function() { - it("should load settings from DB when environment variables are not set", async function() { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings are not found", async function () { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Settings not found"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should add its method and service name to error if not present", async function() { - const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal(SERVICE_NAME); - expect(error.method).to.equal("loadSettings"); - } - }); - - it("should not add its method and service name to error if present", async function() { - const error = new Error("Test error"); - error.method = "otherMethod"; - error.service = "OTHER_SERVICE"; - const appSettings = { findOne: sinon.stub().throws(error) }; - const settingsService = new SettingsService(appSettings); - try { - await settingsService.loadSettings(); - } catch (error) { - expect(error.message).to.equal("Test error"); - expect(error.service).to.equal("OTHER_SERVICE"); - expect(error.method).to.equal("otherMethod"); - } - }); - - it("should merge DB settings with environment variables", async function () { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - const result = await settingsService.loadSettings(); - expect(result).to.deep.equal(settingsService.settings); - expect(settingsService.settings.logLevel).to.equal("debug"); - expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost"); - }); - }); - - describe("reloadSettings", function() { - it("should call loadSettings", async function() { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = {}; - const result = await settingsService.reloadSettings(); - expect(result).to.deep.equal(dbSettings); - }); - }); - - describe("getSettings", function() { - it("should return the current settings", function() { - const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; - const appSettings = { findOne: sinon.stub().returns(dbSettings) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = dbSettings; - const result = settingsService.getSettings(); - expect(result).to.deep.equal(dbSettings); - }); - - it("should throw an error if settings have not been loaded", function() { - const appSettings = { findOne: sinon.stub().returns(null) }; - const settingsService = new SettingsService(appSettings); - settingsService.settings = null; - - try { - settingsService.getSettings(); - } catch (error) { - expect(error.message).to.equal("Settings have not been loaded"); - } - }); - }); -}); diff --git a/Server/tests/services/statusService.test.js b/Server/tests/services/statusService.test.js deleted file mode 100644 index f7473e37e..000000000 --- a/Server/tests/services/statusService.test.js +++ /dev/null @@ -1,259 +0,0 @@ -import sinon from "sinon"; -import StatusService from "../../service/statusService.js"; -import { afterEach, describe } from "node:test"; - -describe("StatusService", () => { - let db, logger, statusService; - - beforeEach(function() { - db = { - getMonitorById: sinon.stub(), - createCheck: sinon.stub(), - createPagespeedCheck: sinon.stub(), - }; - logger = { - info: sinon.stub(), - error: sinon.stub(), - }; - statusService = new StatusService(db, logger); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe("constructor", () => { - it("should create an instance of StatusService", function() { - expect(statusService).to.be.an.instanceOf(StatusService); - }); - }); - - describe("getStatusString", () => { - it("should return 'up' if status is true", function() { - expect(statusService.getStatusString(true)).to.equal("up"); - }); - - it("should return 'down' if status is false", function() { - expect(statusService.getStatusString(false)).to.equal("down"); - }); - - it("should return 'unknown' if status is undefined or null", function() { - expect(statusService.getStatusString(undefined)).to.equal("unknown"); - }); - }); - - describe("updateStatus", () => { - beforeEach(function() { - // statusService.insertCheck = sinon.stub().resolves; - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should throw an error if an error occurs", async function() { - const error = new Error("Test error"); - statusService.db.getMonitorById = sinon.stub().throws(error); - try { - await statusService.updateStatus({ monitorId: "test", status: true }); - } catch (error) { - expect(error.message).to.equal("Test error"); - } - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: false} if status hasn't changed", async function() { - statusService.db.getMonitorById = sinon.stub().returns({ status: true }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result).to.deep.equal({ statusChanged: false }); - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from down to up", async function() { - statusService.db.getMonitorById = sinon - .stub() - .returns({ status: false, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: true, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.true; - expect(result.prevStatus).to.be.false; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - - it("should return {statusChanged: true} if status has changed from up to down", async function() { - statusService.db.getMonitorById = sinon - .stub() - .returns({ status: true, save: sinon.stub() }); - const result = await statusService.updateStatus({ - monitorId: "test", - status: false, - }); - expect(result.statusChanged).to.be.true; - expect(result.monitor.status).to.be.false; - expect(result.prevStatus).to.be.true; - // expect(statusService.insertCheck.calledOnce).to.be.true; - }); - }); - - describe("buildCheck", () => { - it("should build a check object", function() { - const check = statusService.buildCheck({ - monitorId: "test", - type: "test", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { test: "test" }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - }); - - it("should build a check object for pagespeed type", function() { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: { - accessibility: { score: 1 }, - "best-practices": { score: 1 }, - performance: { score: 1 }, - seo: { score: 1 }, - }, - audits: { - "cumulative-layout-shift": { score: 1 }, - "speed-index": { score: 1 }, - "first-contentful-paint": { score: 1 }, - "largest-contentful-paint": { score: 1 }, - "total-blocking-time": { score: 1 }, - }, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(100); - expect(check.bestPractices).to.equal(100); - expect(check.performance).to.equal(100); - expect(check.seo).to.equal(100); - expect(check.audits).to.deep.equal({ - cls: { score: 1 }, - si: { score: 1 }, - fcp: { score: 1 }, - lcp: { score: 1 }, - tbt: { score: 1 }, - }); - }); - - it("should build a check object for pagespeed type with missing data", function() { - const check = statusService.buildCheck({ - monitorId: "test", - type: "pagespeed", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { - lighthouseResult: { - categories: {}, - audits: {}, - }, - }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.accessibility).to.equal(0); - expect(check.bestPractices).to.equal(0); - expect(check.performance).to.equal(0); - expect(check.seo).to.equal(0); - expect(check.audits).to.deep.equal({ - cls: 0, - si: 0, - fcp: 0, - lcp: 0, - tbt: 0, - }); - }); - - it("should build a check for hardware type", function() { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: { data: { cpu: "cpu", memory: "memory", disk: "disk", host: "host" } }, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.equal("cpu"); - expect(check.memory).to.equal("memory"); - expect(check.disk).to.equal("disk"); - expect(check.host).to.equal("host"); - }); - - it("should build a check for hardware type with missing data", function() { - const check = statusService.buildCheck({ - monitorId: "test", - type: "hardware", - status: true, - responseTime: 100, - code: 200, - message: "Test message", - payload: {}, - }); - expect(check.monitorId).to.equal("test"); - expect(check.status).to.be.true; - expect(check.statusCode).to.equal(200); - expect(check.responseTime).to.equal(100); - expect(check.message).to.equal("Test message"); - expect(check.cpu).to.deep.equal({}); - expect(check.memory).to.deep.equal({}); - expect(check.disk).to.deep.equal({}); - expect(check.host).to.deep.equal({}); - }); - }); - - describe("insertCheck", () => { - it("should log an error if one is thrown", async function() { - const testError = new Error("Test error"); - statusService.db.createCheck = sinon.stub().throws(testError); - try { - await statusService.insertCheck({ monitorId: "test" }); - } catch (error) { - expect(error.message).to.equal(testError.message); - } - expect(statusService.logger.error.calledOnce).to.be.true; - }); - - it("should insert a check into the database", async function() { - await statusService.insertCheck({ monitorId: "test", type: "http" }); - expect(statusService.db.createCheck.calledOnce).to.be.true; - }); - }); -}); diff --git a/Server/tests/utils/dataUtils.test.js b/Server/tests/utils/dataUtils.test.js deleted file mode 100644 index 0b73a5f95..000000000 --- a/Server/tests/utils/dataUtils.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js"; -import sinon from "sinon"; - -describe("NormalizeData", function() { - it("should normalize response times when checks length is greater than 1", function() { - const checks = [ - { responseTime: 20, _doc: { id: 1 } }, - { responseTime: 40, _doc: { id: 2 } }, - { responseTime: 60, _doc: { id: 3 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(3); - result.forEach((check) => { - expect(check).to.have.property("responseTime").that.is.a("number"); - expect(check).to.have.property("originalResponseTime").that.is.a("number"); - }); - }); - - it("should return checks with original response times when checks length is 1", function() { - const checks = [{ responseTime: 20, _doc: { id: 1 } }]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(1); - expect(result[0]).to.have.property("originalResponseTime", 20); - }); - - it("should handle edge cases with extreme response times", function() { - const checks = [ - { responseTime: 5, _doc: { id: 1 } }, - { responseTime: 95, _doc: { id: 2 } }, - ]; - const rangeMin = 1; - const rangeMax = 100; - - const result = NormalizeData(checks, rangeMin, rangeMax); - - expect(result).to.be.an("array"); - expect(result).to.have.lengthOf(2); - expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin); - expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax); - }); -}); - -describe("calculatePercentile", function() { - it("should return the lower value when upper is greater than or equal to the length of the sorted array", function() { - const checks = [ - { responseTime: 10 }, - { responseTime: 20 }, - { responseTime: 30 }, - { responseTime: 40 }, - { responseTime: 50 }, - ]; - - const percentile = 100; - const result = calculatePercentile(checks, percentile); - const expected = 50; - expect(result).to.equal(expected); - }); -}); diff --git a/Server/tests/utils/imageProcessing.test.js b/Server/tests/utils/imageProcessing.test.js deleted file mode 100644 index 7a779c93d..000000000 --- a/Server/tests/utils/imageProcessing.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import { expect } from "chai"; -import sinon from "sinon"; -import sharp from "sharp"; -import { GenerateAvatarImage } from "../../utils/imageProcessing.js"; - -describe("imageProcessing - GenerateAvatarImage", function() { - it("should resize the image to 64x64 and return a base64 string", async function() { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function - const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon - .stub(sharp.prototype, "resize") - .returns({ toBuffer: toBufferStub }); - - const result = await GenerateAvatarImage(file); - - // Verify the result - const expected = Buffer.from("resized image buffer").toString("base64"); - expect(result).to.equal(expected); - - // Verify that the sharp function was called with the correct arguments - expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true; - expect(toBufferStub.calledOnce).to.be.true; - - // Restore the stubbed functions - sharpStub.restore(); - }); - - it("should throw an error if resizing fails", async function() { - const file = { - buffer: Buffer.from("test image buffer"), - }; - - // Stub the sharp function to throw an error - const toBufferStub = sinon.stub().rejects(new Error("Resizing failed")); - const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); - const sharpStub = sinon - .stub(sharp.prototype, "resize") - .returns({ toBuffer: toBufferStub }); - - try { - await GenerateAvatarImage(file); - // If no error is thrown, fail the test - expect.fail("Expected error to be thrown"); - } catch (error) { - // Verify that the error message is correct - expect(error.message).to.equal("Resizing failed"); - } - - // Restore the stubbed functions - sharpStub.restore(); - }); -}); diff --git a/Server/tests/utils/logger.test.js b/Server/tests/utils/logger.test.js deleted file mode 100644 index 8030ff8fb..000000000 --- a/Server/tests/utils/logger.test.js +++ /dev/null @@ -1,139 +0,0 @@ -import sinon from "sinon"; -import logger from "../../utils/logger.js"; -import { Logger } from "../../utils/logger.js"; -import winston from "winston"; - -describe("Logger", function() { - let infoStub, warnStub, errorStub; - - beforeEach(function() { - infoStub = sinon.stub(logger.logger, "info"); - warnStub = sinon.stub(logger.logger, "warn"); - errorStub = sinon.stub(logger.logger, "error"); - }); - - afterEach(function() { - sinon.restore(); - }); - - describe("constructor", function() { - let createLoggerStub; - - beforeEach(function () { - createLoggerStub = sinon.stub(winston, "createLogger"); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should convert message to JSON string if it is an object", function () { - const logMessage = { key: "value" }; - const expectedMessage = JSON.stringify(logMessage, null, 2); - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: logMessage, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedMessage); - return { log: sinon.spy() }; - }); - - const logger = new Logger(); - logger.logger.info(logMessage); - }); - - it("should convert details to JSON string if it is an object", function () { - const logDetails = { key: "value" }; - const expectedDetails = JSON.stringify(logDetails, null, 2); // Removed .s - - createLoggerStub.callsFake((config) => { - const consoleTransport = config.transports[0]; - const logEntry = { - level: "info", - message: "", // Add empty message since it's required - details: logDetails, - timestamp: new Date().toISOString(), - }; - const formattedMessage = consoleTransport.format.transform(logEntry); - expect(formattedMessage).to.include(expectedDetails); - return { info: sinon.spy() }; // Changed to return info method - }); - - const logger = new Logger(); - logger.logger.info("", { details: logDetails }); // Updated to pass details properly - }); - }); - - describe("info", function() { - it("should log an informational message", function() { - const config = { - message: "Info message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.info(config); - - expect(infoStub.calledOnce).to.be.true; - expect( - infoStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("warn", function() { - it("should log a warning message", function() { - const config = { - message: "Warning message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - }; - - logger.warn(config); - - expect(warnStub.calledOnce).to.be.true; - expect( - warnStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - }) - ).to.be.true; - }); - }); - - describe("error", function() { - it("should log an error message", function() { - const config = { - message: "Error message", - service: "TestService", - method: "TestMethod", - details: { key: "value" }, - stack: "Error stack trace", - }; - - logger.error(config); - - expect(errorStub.calledOnce).to.be.true; - expect( - errorStub.calledWith(config.message, { - service: config.service, - method: config.method, - details: config.details, - stack: config.stack, - }) - ).to.be.true; - }); - }); -}); diff --git a/Server/tests/utils/messages.test.js b/Server/tests/utils/messages.test.js deleted file mode 100644 index 7ece332c4..000000000 --- a/Server/tests/utils/messages.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { errorMessages, successMessages } from "../../utils/messages.js"; -describe("Messages", function() { - describe("messages - errorMessages", function() { - it("should have a DB_FIND_MONITOR_BY_ID function", function() { - const monitorId = "12345"; - expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal( - `Monitor with id ${monitorId} not found` - ); - }); - - it("should have a DB_DELETE_CHECKS function", function() { - const monitorId = "12345"; - expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal( - `No checks found for monitor with id ${monitorId}` - ); - }); - }); - - describe("messages - successMessages", function() { - it("should have a MONITOR_GET_BY_USER_ID function", function() { - const userId = "12345"; - expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal( - `Got monitor for ${userId} successfully"` - ); - }); - - // Add more tests for other success messages as needed - }); -}); diff --git a/Server/tests/utils/utils.test.js b/Server/tests/utils/utils.test.js deleted file mode 100644 index f697c9b4a..000000000 --- a/Server/tests/utils/utils.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js"; - -describe("utils - ParseBoolean", function() { - it("should return true", function() { - const result = ParseBoolean("true"); - expect(result).to.be.true; - }); - - it("should return false", function() { - const result = ParseBoolean("false"); - expect(result).to.be.false; - }); - - it("should return false", function() { - const result = ParseBoolean(null); - expect(result).to.be.false; - }); - - it("should return false", function() { - const result = ParseBoolean(undefined); - expect(result).to.be.false; - }); -}); - -describe("utils - getTokenFromHeaders", function() { - it("should throw an error if authorization header is missing", function() { - const headers = {}; - expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers"); - }); - - it("should throw an error if authorization header does not start with Bearer", function() { - const headers = { authorization: "Basic abcdef" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should return the token if authorization header is correctly formatted", function() { - const headers = { authorization: "Bearer abcdef" }; - expect(getTokenFromHeaders(headers)).to.equal("abcdef"); - }); - - it("should throw an error if authorization header has more than two parts", function() { - const headers = { authorization: "Bearer abc def" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); - - it("should throw an error if authorization header has less than two parts", function() { - const headers = { authorization: "Bearer" }; - expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); - }); -}); diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js deleted file mode 100644 index ca6ca66df..000000000 --- a/Server/utils/dataUtils.js +++ /dev/null @@ -1,92 +0,0 @@ -const calculatePercentile = (arr, percentile) => { - const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); - const index = (percentile / 100) * (sorted.length - 1); - const lower = Math.floor(index); - const upper = lower + 1; - const weight = index % 1; - if (upper >= sorted.length) return sorted[lower].responseTime; - return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight; -}; - -const calculatePercentileUptimeDetails = (arr, percentile) => { - const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime); - const index = (percentile / 100) * (sorted.length - 1); - const lower = Math.floor(index); - const upper = lower + 1; - const weight = index % 1; - if (upper >= sorted.length) return sorted[lower].avgResponseTime; - return ( - sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight - ); -}; - -const NormalizeData = (checks, rangeMin, rangeMax) => { - if (checks.length > 1) { - // Get the 5th and 95th percentile - const min = calculatePercentile(checks, 0); - const max = calculatePercentile(checks, 95); - const normalizedChecks = checks.map((check) => { - const originalResponseTime = check.responseTime; - // Normalize the response time between 1 and 100 - let normalizedResponseTime = - rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min); - - // Put a floor on the response times so we don't have extreme outliers - // Better visuals - normalizedResponseTime = Math.max( - rangeMin, - Math.min(rangeMax, normalizedResponseTime) - ); - return { - ...check, - responseTime: normalizedResponseTime, - originalResponseTime: originalResponseTime, - }; - }); - - return normalizedChecks; - } else { - return checks.map((check) => { - return { ...check, originalResponseTime: check.responseTime }; - }); - } -}; -const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => { - if (checks.length > 1) { - // Get the 5th and 95th percentile - const min = calculatePercentileUptimeDetails(checks, 0); - const max = calculatePercentileUptimeDetails(checks, 95); - - const normalizedChecks = checks.map((check) => { - const originalResponseTime = check.avgResponseTime; - // Normalize the response time between 1 and 100 - let normalizedResponseTime = - rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min); - - // Put a floor on the response times so we don't have extreme outliers - // Better visuals - normalizedResponseTime = Math.max( - rangeMin, - Math.min(rangeMax, normalizedResponseTime) - ); - return { - ...check, - avgResponseTime: normalizedResponseTime, - originalAvgResponseTime: originalResponseTime, - }; - }); - - return normalizedChecks; - } else { - return checks.map((check) => { - return { ...check, originalResponseTime: check.responseTime }; - }); - } -}; - -export { - calculatePercentile, - NormalizeData, - calculatePercentileUptimeDetails, - NormalizeDataUptimeDetails, -}; diff --git a/Server/utils/demoMonitors.json b/Server/utils/demoMonitors.json deleted file mode 100644 index 3efea8d0e..000000000 --- a/Server/utils/demoMonitors.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "Google", - "url": "https://www.google.com" - }, - { - "name": "Facebook", - "url": "https://www.facebook.com" - }, - { - "name": "Yahoo", - "url": "https://www.yahoo.com" - }, - { - "name": "Amazon", - "url": "https://www.amazon.com" - }, - { - "name": "Apple", - "url": "https://www.apple.com" - } -] diff --git a/Server/utils/demoMonitorsOld.json b/Server/utils/demoMonitorsOld.json deleted file mode 100644 index ffce7d226..000000000 --- a/Server/utils/demoMonitorsOld.json +++ /dev/null @@ -1,1271 +0,0 @@ -[ - { - "name": "0to255", - "url": "https://0to255.com" - }, - { - "name": "10015.io", - "url": "https://10015.io" - }, - { - "name": "3DIcons", - "url": "https://3dicons.co" - }, - { - "name": "About.me", - "url": "https://about.me" - }, - { - "name": "Alias", - "url": "https://alias.co" - }, - { - "name": "All About Berlin", - "url": "https://allaboutberlin.com" - }, - { - "name": "All Acronyms", - "url": "https://allacronyms.com" - }, - { - "name": "All You Can Read ", - "url": "https://allyoucanread.com" - }, - { - "name": "AllTrails", - "url": "https://alltrails.com" - }, - { - "name": "Anotepad", - "url": "https://anotepad.com" - }, - { - "name": "AnswerSocrates", - "url": "https://answersocrates.com" - }, - { - "name": "AnswerThePublic ", - "url": "https://answerthepublic.com" - }, - { - "name": "Apollo ", - "url": "https://apollo.io" - }, - { - "name": "ArrayList", - "url": "https://arraylist.org" - }, - { - "name": "Ask Difference", - "url": "https://askdifference.com" - }, - { - "name": "Audd.io", - "url": "https://audd.io" - }, - { - "name": "Audiocheck", - "url": "https://audiocheck.net" - }, - { - "name": "Audionautix", - "url": "https://audionautix.com" - }, - { - "name": "Authentic Jobs", - "url": "https://authenticjobs.com" - }, - { - "name": "Behind the Name", - "url": "https://behindthename.com" - }, - { - "name": "Bilim Terimleri", - "url": "https://terimler.org" - }, - { - "name": "BitBof", - "url": "https://bitbof.com" - }, - { - "name": "Blank Page", - "url": "https://blank.page" - }, - { - "name": "Bonanza", - "url": "https://bonanza.com" - }, - { - "name": "BookCrossing", - "url": "https://bookcrossing.com" - }, - { - "name": "Browse AI", - "url": "https://browse.ai" - }, - { - "name": "Bubbl.us", - "url": "https://bubbl.us" - }, - { - "name": "Business Model Toolbox", - "url": "https://bmtoolbox.net" - }, - { - "name": "ByClickDownloader", - "url": "https://byclickdownloader.com" - }, - { - "name": "Calligraphr", - "url": "https://calligraphr.com" - }, - { - "name": "CertificateClaim", - "url": "https://certificateclaim.com" - }, - { - "name": "Chosic", - "url": "https://chosic.com" - }, - { - "name": "ClipDrop", - "url": "https://clipdrop.co" - }, - { - "name": "CloudConvert", - "url": "https://cloudconvert.com" - }, - { - "name": "CodingFont", - "url": "https://codingfont.com" - }, - { - "name": "Color Hunt", - "url": "https://colorhunt.co" - }, - { - "name": "ColorHexa", - "url": "https://colorhexa.com" - }, - { - "name": "Conversion-Tool", - "url": "https://conversion-tool.com" - }, - { - "name": "Cool Startup Jobs", - "url": "https://coolstartupjobs.com" - }, - { - "name": "Coroflot", - "url": "https://coroflot.com" - }, - { - "name": "Corrupt-a-File", - "url": "https://corrupt-a-file.net" - }, - { - "name": "Couchsurfing", - "url": "https://couchsurfing.com" - }, - { - "name": "Countries Been", - "url": "https://countriesbeen.com" - }, - { - "name": "Country Code", - "url": "https://countrycode.org" - }, - { - "name": "Creately", - "url": "https://creately.com" - }, - { - "name": "Creately ", - "url": "https://creately.com" - }, - { - "name": "Crossfade.io", - "url": "https://crossfade.io" - }, - { - "name": "Crunchbase", - "url": "https://crunchbase.com" - }, - { - "name": "CVmkr", - "url": "https://cvwizard.com" - }, - { - "name": "Daily Remote", - "url": "https://dailyremote.com" - }, - { - "name": "David Li", - "url": "https://david.li" - }, - { - "name": "DemandHunt", - "url": "https://demandhunt.com" - }, - { - "name": "Designify", - "url": "https://designify.com" - }, - { - "name": "Diff Checker", - "url": "https://diffchecker.com" - }, - { - "name": "DifferenceBetween.info", - "url": "https://differencebetween.info" - }, - { - "name": "Digital Glossary", - "url": "https://digital-glossary.com" - }, - { - "name": "Dimensions", - "url": "https://dimensions.com" - }, - { - "name": "Discoverify Music", - "url": "https://discoverifymusic.com" - }, - { - "name": "discu.eu", - "url": "https://discu.eu" - }, - { - "name": "Do It Yourself", - "url": "https://doityourself.com" - }, - { - "name": "draw.io", - "url": "https://drawio.com" - }, - { - "name": "Drumeo", - "url": "https://drumeo.com" - }, - { - "name": "Dummies", - "url": "https://dummies.com" - }, - { - "name": "Easel.ly", - "url": "https://easel.ly" - }, - { - "name": "Educalingo", - "url": "https://educalingo.com" - }, - { - "name": "Emoji Combos", - "url": "https://emojicombos.com" - }, - { - "name": "EquityBee", - "url": "https://equitybee.com" - }, - { - "name": "EquityZen", - "url": "https://equityzen.com" - }, - { - "name": "Escape Room Tips", - "url": "https://escaperoomtips.com" - }, - { - "name": "Every Noise", - "url": "https://everynoise.com" - }, - { - "name": "Every Time Zone", - "url": "https://everytimezone.com" - }, - { - "name": "Excalideck", - "url": "https://excalideck.com" - }, - { - "name": "Excalidraw", - "url": "https://excalidraw.com" - }, - { - "name": "Extract pics", - "url": "https://extract.pics" - }, - { - "name": "EZGIF", - "url": "https://ezgif.com" - }, - { - "name": "FactSlides", - "url": "https://factslides.com" - }, - { - "name": "FIGR ", - "url": "https://figr.app" - }, - { - "name": "Fine Dictionary", - "url": "https://finedictionary.com" - }, - { - "name": "Fiverr", - "url": "https://fiverr.com" - }, - { - "name": "Fix It Club", - "url": "https://fixitclub.com" - }, - { - "name": "Flightradar24", - "url": "https://flightradar24.com" - }, - { - "name": "FlowCV ", - "url": "https://flowcv.com" - }, - { - "name": "Font Squirrel", - "url": "https://fontsquirrel.com" - }, - { - "name": "FontAwesome", - "url": "https://fontawesome.com" - }, - { - "name": "Fontello ", - "url": "https://fontello.com" - }, - { - "name": "Form to Chatbot", - "url": "https://formtochatbot.com" - }, - { - "name": "Founder Resources", - "url": "https://founderresources.io" - }, - { - "name": "Franz", - "url": "https://meetfranz.com" - }, - { - "name": "Fraze It", - "url": "https://fraze.it" - }, - { - "name": "Freecycle", - "url": "https://freecycle.org" - }, - { - "name": "FreeType", - "url": "https://freetype.org" - }, - { - "name": "FutureM", - "url": "https://futureme.org" - }, - { - "name": "Generated.Photos", - "url": "https://generated.photos" - }, - { - "name": "Get Human", - "url": "https://gethuman.com" - }, - { - "name": "Go Bento", - "url": "https://gobento.com" - }, - { - "name": "Good CV", - "url": "https://goodcv.com" - }, - { - "name": "Grammar Monster", - "url": "https://grammar-monster.com" - }, - { - "name": "Grammar Book", - "url": "https://grammarbook.com" - }, - { - "name": "Gummy Search", - "url": "https://gummysearch.com" - }, - { - "name": "Gumroad", - "url": "https://gumroad.com" - }, - { - "name": "HealthIcons", - "url": "https://healthicons.org" - }, - { - "name": "HexColor", - "url": "https://hexcolor.co" - }, - { - "name": "Hidden Life Radio", - "url": "https://hiddenliferadio.com" - }, - { - "name": "Hired", - "url": "https://lhh.com" - }, - { - "name": "Honey", - "url": "https://joinhoney.com" - }, - { - "name": "HowStuffWorks", - "url": "https://howstuffworks.com" - }, - { - "name": "HugeIcons Pro", - "url": "https://hugeicons.com" - }, - { - "name": "Humble Bundle", - "url": "https://humblebundle.com" - }, - { - "name": "I Have No TV", - "url": "https://ihavenotv.com" - }, - { - "name": "I Miss the Office", - "url": "https://imisstheoffice.eu" - }, - { - "name": "IcoMoon", - "url": "https://icomoon.io" - }, - { - "name": "Iconfinder", - "url": "https://iconfinder.com" - }, - { - "name": "Icon Packs", - "url": "https://iconpacks.net" - }, - { - "name": "Iconshock", - "url": "https://iconshock.com" - }, - { - "name": "Iconz Design", - "url": "https://iconz.design" - }, - { - "name": "iFixit", - "url": "https://ifixit.com" - }, - { - "name": "IFTTT", - "url": "https://ifttt.com" - }, - { - "name": "Illlustrations", - "url": "https://illlustrations.co" - }, - { - "name": "Illustration Kit", - "url": "https://illustrationkit.com" - }, - { - "name": "IMSDB", - "url": "https://imsdb.com" - }, - { - "name": "Incompetech", - "url": "https://incompetech.com" - }, - { - "name": "Incredibox", - "url": "https://incredibox.com" - }, - { - "name": "InnerBod", - "url": "https://innerbody.com" - }, - { - "name": "Instructables", - "url": "https://instructables.com" - }, - { - "name": "Integromat", - "url": "https://make.com" - }, - { - "name": "Investopedia", - "url": "https://investopedia.com" - }, - { - "name": "Japanese Wiki Corpus", - "url": "https://japanesewiki.com" - }, - { - "name": "Jitter.Video", - "url": "https://jitter.video" - }, - { - "name": "Jobspresso", - "url": "https://jobspresso.co" - }, - { - "name": "JPEG-Optimizer", - "url": "https://jpeg-optimizer.com" - }, - { - "name": "JS Remotely", - "url": "https://jsremotely.com" - }, - { - "name": "JScreenFix", - "url": "https://jscreenfix.com" - }, - { - "name": "JSON Resume", - "url": "https://jsonresume.io" - }, - { - "name": "Just Join", - "url": "https://justjoin.it" - }, - { - "name": "Just the Recipe", - "url": "https://justtherecipe.com" - }, - { - "name": "JustRemote", - "url": "https://justremote.co" - }, - { - "name": "JustWatch", - "url": "https://justwatch.com" - }, - { - "name": "Kanopy", - "url": "https://kanopy.com" - }, - { - "name": "Kassellabs", - "url": "https://kassellabs.io" - }, - { - "name": "Key Differences", - "url": "https://keydifferences.com" - }, - { - "name": "Keybase", - "url": "https://keybase.io" - }, - { - "name": "KeyValues", - "url": "https://keyvalues.com" - }, - { - "name": "KHInsider", - "url": "https://khinsider.com" - }, - { - "name": "Killed by Google", - "url": "https://killedbygoogle.com" - }, - { - "name": "Kimovil", - "url": "https://kimovil.com" - }, - { - "name": "Lalal.ai", - "url": "https://www.lalal.ai" - }, - { - "name": "Learn Anything", - "url": "https://learn-anything.xyz" - }, - { - "name": "LendingTree", - "url": "https://lendingtree.com" - }, - { - "name": "Lightyear.fm", - "url": "https://lightyear.fm" - }, - { - "name": "LittleSis", - "url": "https://littlesis.org" - }, - { - "name": "Looria", - "url": "https://looria.com" - }, - { - "name": "Lucidchart", - "url": "https://lucidchart.com" - }, - { - "name": "Lunar", - "url": "https://lunar.fyi" - }, - { - "name": "Manuals Lib", - "url": "https://manualslib.com" - }, - { - "name": "Map Crunch", - "url": "https://mapcrunch.com" - }, - { - "name": "Masterworks", - "url": "https://masterworks.com" - }, - { - "name": "MediaFire", - "url": "https://mediafire.com" - }, - { - "name": "Mixlr", - "url": "https://mixlr.com" - }, - { - "name": "Moises AI", - "url": "https://moises.ai" - }, - { - "name": "Money", - "url": "https://money.com" - }, - { - "name": "Mountain Project", - "url": "https://mountainproject.com" - }, - { - "name": "Movie Map", - "url": "https://movie-map.com" - }, - { - "name": "Movie Sounds", - "url": "https://movie-sounds.org" - }, - { - "name": "MP3Cut", - "url": "https://mp3cut.net" - }, - { - "name": "Murmel", - "url": "https://murmel.social" - }, - { - "name": "Muscle Wiki", - "url": "https://musclewiki.com" - }, - { - "name": "Music-Map", - "url": "https://music-map.com" - }, - { - "name": "MusicTheory.net", - "url": "https://musictheory.net" - }, - { - "name": "MyFonts", - "url": "https://myfonts.com" - }, - { - "name": "MyFridgeFood", - "url": "https://myfridgefood.com" - }, - { - "name": "Nameberry", - "url": "https://nameberry.com" - }, - { - "name": "Namechk", - "url": "https://namechk.com" - }, - { - "name": "Ncase", - "url": "https://ncase.me" - }, - { - "name": "News in Levels", - "url": "https://newsinlevels.com" - }, - { - "name": "Noisli", - "url": "https://noisli.com" - }, - { - "name": "Notes.io", - "url": "https://notes.io" - }, - { - "name": "Novoresume", - "url": "https://novoresume.com" - }, - { - "name": "Ocoya", - "url": "https://ocoya.com" - }, - { - "name": "Old Computers Museum", - "url": "https://oldcomputers.net" - }, - { - "name": "Online Tone Generator", - "url": "https://onlinetonegenerator.com" - }, - { - "name": "Online-Convert", - "url": "https://online-convert.com" - }, - { - "name": "OnlineConversion", - "url": "https://onlineconversion.com" - }, - { - "name": "Online OCR", - "url": "https://onlineocr.net" - }, - { - "name": "OpenWeatherMap", - "url": "https://openweathermap.org" - }, - { - "name": "OrgPad", - "url": "https://orgpad.com" - }, - { - "name": "Passport Index", - "url": "https://passportindex.org" - }, - { - "name": "PDF Candy", - "url": "https://pdfcandy.com" - }, - { - "name": "PDF2DOC", - "url": "https://pdf2doc.com" - }, - { - "name": "PDFescape", - "url": "https://pdfescape.com" - }, - { - "name": "PfpMaker", - "url": "https://pfpmaker.com" - }, - { - "name": "PIDGI Wiki ", - "url": "https://pidgi.net" - }, - { - "name": "PimEyes", - "url": "https://pimeyes.com" - }, - { - "name": "Pipl ", - "url": "https://pipl.com" - }, - { - "name": "PixelBazaar", - "url": "https://pixelbazaar.com" - }, - { - "name": "PixelPaper", - "url": "https://pixelpaper.io" - }, - { - "name": "Ponly", - "url": "https://ponly.com" - }, - { - "name": "PowerToFly", - "url": "https://powertofly.com" - }, - { - "name": "Pretzel Rocks", - "url": "https://pretzel.rocks" - }, - { - "name": "PrintIt", - "url": "https://printit.work" - }, - { - "name": "Prismatext", - "url": "https://prismatext.com" - }, - { - "name": "Puffin Maps", - "url": "https://puffinmaps.com" - }, - { - "name": "Puzzle Loop ", - "url": "https://puzzle-loop.com" - }, - { - "name": "QuoteMaster", - "url": "https://quotemaster.org" - }, - { - "name": "Radio Garden", - "url": "https://radio.garden" - }, - { - "name": "Radiooooo", - "url": "https://radiooooo.com" - }, - { - "name": "Radiosondy", - "url": "https://radiosondy.info" - }, - { - "name": "Rainy Mood", - "url": "https://rainymood.com" - }, - { - "name": "Random Street View", - "url": "https://randomstreetview.com" - }, - { - "name": "Rap4Ever", - "url": "https://rap4all.com" - }, - { - "name": "RareFilm", - "url": "https://rarefilm.net" - }, - { - "name": "Rattibha", - "url": "https://rattibha.com" - }, - { - "name": "Reddit List ", - "url": "https://redditlist.com" - }, - { - "name": "RedditSearch.io", - "url": "https://redditsearch.io" - }, - { - "name": "Reelgood", - "url": "https://reelgood.com" - }, - { - "name": "Reface", - "url": "https://reface.ai" - }, - { - "name": "Rejected.us", - "url": "https://rejected.us" - }, - { - "name": "Relanote", - "url": "https://relanote.com" - }, - { - "name": "Remote Leaf", - "url": "https://remoteleaf.com" - }, - { - "name": "Remote OK", - "url": "https://remoteok.com" - }, - { - "name": "Remote Starter Kit ", - "url": "https://remotestarterkit.com" - }, - { - "name": "Remote.co", - "url": "https://remote.co" - }, - { - "name": "Remote Base ", - "url": "https://remotebase.com" - }, - { - "name": "Remote Bear", - "url": "https://remotebear.io" - }, - { - "name": "Remove.bg", - "url": "https://remove.bg" - }, - { - "name": "Respresso", - "url": "https://respresso.io" - }, - { - "name": "Reveddit", - "url": "https://reveddit.com" - }, - { - "name": "Rhymer", - "url": "https://rhymer.com" - }, - { - "name": "RhymeZone", - "url": "https://rhymezone.com" - }, - { - "name": "Ribbet", - "url": "https://ribbet.com" - }, - { - "name": "Roadmap.sh", - "url": "https://roadmap.sh" - }, - { - "name": "Roadtrippers", - "url": "https://roadtrippers.com" - }, - { - "name": "RxResu.me", - "url": "https://rxresu.me" - }, - { - "name": "SchemeColor", - "url": "https://schemecolor.com" - }, - { - "name": "Screenshot.Guru", - "url": "https://screenshot.guru" - }, - { - "name": "SeatGuru", - "url": "https://seatguru.com" - }, - { - "name": "Sessions", - "url": "https://sessions.us" - }, - { - "name": "Shottr", - "url": "https://shottr.cc" - }, - { - "name": "Signature Maker", - "url": "https://signature-maker.net" - }, - { - "name": "Skip The Drive", - "url": "https://skipthedrive.com" - }, - { - "name": "Slowby", - "url": "https://slowby.travel" - }, - { - "name": "Small World", - "url": "https://smallworld.kiwi" - }, - { - "name": "SmallPDF", - "url": "https://smallpdf.com" - }, - { - "name": "Social Image Maker", - "url": "https://socialimagemaker.io" - }, - { - "name": "Social Sizes", - "url": "https://socialsizes.io" - }, - { - "name": "SoundLove", - "url": "https://soundlove.se" - }, - { - "name": "Spline", - "url": "https://spline.design" - }, - { - "name": "Starkey Comics", - "url": "https://starkeycomics.com" - }, - - { - "name": "Statista", - "url": "https://statista.com" - }, - { - "name": "Stolen Camera Finder", - "url": "https://stolencamerafinder.com" - }, - { - "name": "Strobe.Cool", - "url": "https://strobe.cool" - }, - { - "name": "Sumo", - "url": "https://sumo.app" - }, - { - "name": "SuperMeme AI", - "url": "https://supermeme.ai" - }, - { - "name": "Synthesia", - "url": "https://synthesia.io" - }, - { - "name": "TablerIcons", - "url": "https://tablericons.com" - }, - { - "name": "Tango", - "url": "https://tango.us" - }, - { - "name": "TasteDive", - "url": "https://tastedive.com" - }, - { - "name": "TechSpecs", - "url": "https://techspecs.io" - }, - { - "name": "Teoria", - "url": "https://teoria.com" - }, - { - "name": "Text Faces", - "url": "https://textfac.es" - }, - { - "name": "The Balance Money", - "url": "https://thebalancemoney.com" - }, - { - "name": "The Punctuation Guide", - "url": "https://thepunctuationguide.com" - }, - { - "name": "This to That", - "url": "https://thistothat.com" - }, - { - "name": "This vs That", - "url": "https://thisvsthat.io" - }, - { - "name": "ThreadReaderApp ", - "url": "https://threadreaderapp.com" - }, - { - "name": "Thumbly", - "url": "https://tokee.ai" - }, - { - "name": "Tiii.me", - "url": "https://tiii.me" - }, - { - "name": "TikTok Video Downloader", - "url": "https://ttvdl.com" - }, - { - "name": "Time and Date", - "url": "https://timeanddate.com" - }, - { - "name": "Time.is", - "url": "https://time.is" - }, - { - "name": "Title Case", - "url": "https://titlecase.com" - }, - { - "name": "Toaster Central", - "url": "https://toastercentral.com" - }, - { - "name": "Tongue-Twister ", - "url": "https://tongue-twister.net" - }, - { - "name": "TradingView", - "url": "https://tradingview.com" - }, - { - "name": "Transparent Textures", - "url": "https://transparenttextures.com" - }, - { - "name": "Tubi TV", - "url": "https://tubitv.com" - }, - { - "name": "Tunefind", - "url": "https://tunefind.com" - }, - { - "name": "TuneMyMusic", - "url": "https://tunemymusic.com" - }, - { - "name": "Tweepsmap", - "url": "https://fedica.com" - }, - { - "name": "Two Peas and Their Pod", - "url": "https://twopeasandtheirpod.com" - }, - { - "name": "Typatone", - "url": "https://typatone.com" - }, - { - "name": "Under Glass", - "url": "https://underglass.io" - }, - { - "name": "UniCorner", - "url": "https://unicorner.news" - }, - { - "name": "Unita", - "url": "https://unita.co" - }, - { - "name": "UnitConverters", - "url": "https://unitconverters.net" - }, - { - "name": "Unreadit", - "url": "https://unreadit.com" - }, - { - "name": "Unscreen", - "url": "https://unscreen.com" - }, - { - "name": "UnTools ", - "url": "https://untools.co" - }, - { - "name": "Upwork", - "url": "https://upwork.com" - }, - { - "name": "UTF8 Icons", - "url": "https://utf8icons.com" - }, - { - "name": "Vector Magic", - "url": "https://vectormagic.com" - }, - { - "name": "Virtual Vacation", - "url": "https://virtualvacation.us" - }, - { - "name": "Virtual Vocations", - "url": "https://virtualvocations.com" - }, - { - "name": "Visiwig", - "url": "https://visiwig.com" - }, - { - "name": "Visual CV", - "url": "https://visualcv.com" - }, - { - "name": "Vocus.io", - "url": "https://vocus.io" - }, - { - "name": "Voscreen", - "url": "https://voscreen.com" - }, - { - "name": "Wanderprep", - "url": "https://wanderprep.com" - }, - { - "name": "Warmshowers", - "url": "https:/warmshowers.org" - }, - { - "name": "Watch Documentaries", - "url": "https://watchdocumentaries.com" - }, - { - "name": "We Work Remotely", - "url": "https://weworkremotely.com" - }, - { - "name": "Web2PDFConvert", - "url": "https://web2pdfconvert.com" - }, - { - "name": "Welcome to My Garden", - "url": "https://welcometomygarden.org" - }, - { - "name": "When2meet ", - "url": "https://when2meet.com" - }, - { - "name": "Where's George", - "url": "https://wheresgeorge.com" - }, - { - "name": "Where's Willy", - "url": "https://whereswilly.com" - }, - { - "name": "WikiHow", - "url": "https://wikihow.com" - }, - { - "name": "Windy", - "url": "https://www.windy.com" - }, - { - "name": "WonderHowTo", - "url": "https://wonderhowto.com" - }, - { - "name": "Working Nomads", - "url": "https://workingnomads.com" - }, - { - "name": "Wormhole", - "url": "https://wormhole.app" - }, - { - "name": "Y Combinator Jobs", - "url": "https://ycombinator.com" - }, - { - "name": "Yes Promo", - "url": "https://yespromo.me" - }, - { - "name": "YouGlish", - "url": "https://youglish.com" - }, - { - "name": "Zamzar", - "url": "https://zamzar.com" - }, - { - "name": "Zippyshare", - "url": "https://zippyshare.com" - }, - { - "name": "Zoom Earth", - "url": "https://zoom.earth" - }, - { - "name": "Zoom.it", - "url": "https://zoom.it" - } -] diff --git a/Server/utils/imageProcessing.js b/Server/utils/imageProcessing.js deleted file mode 100644 index 470a5c27e..000000000 --- a/Server/utils/imageProcessing.js +++ /dev/null @@ -1,25 +0,0 @@ -import sharp from "sharp"; -/** - * Generates a 64 * 64 pixel image from a given image - * @param {} file - */ -const GenerateAvatarImage = async (file) => { - try { - // Resize to target 64 * 64 - let resizedImageBuffer = await sharp(file.buffer) - .resize({ - width: 64, - height: 64, - fit: "cover", - }) - .toBuffer(); - - //Get b64 string - const base64Image = resizedImageBuffer.toString("base64"); - return base64Image; - } catch (error) { - throw error; - } -}; - -export { GenerateAvatarImage }; diff --git a/Server/utils/logger.js b/Server/utils/logger.js deleted file mode 100644 index e263085a7..000000000 --- a/Server/utils/logger.js +++ /dev/null @@ -1,95 +0,0 @@ -import { createLogger, format, transports } from "winston"; - -class Logger { - constructor() { - const consoleFormat = format.printf( - ({ level, message, service, method, details, timestamp }) => { - if (message instanceof Object) { - message = JSON.stringify(message, null, 2); - } - - if (details instanceof Object) { - details = JSON.stringify(details, null, 2); - } - let msg = `${timestamp} ${level}:`; - service && (msg += ` [${service}]`); - method && (msg += `(${method})`); - message && (msg += ` ${message}`); - details && (msg += ` (details: ${details})`); - return msg; - } - ); - - this.logger = createLogger({ - level: "info", - format: format.combine(format.timestamp()), - transports: [ - new transports.Console({ - format: format.combine( - format.colorize(), - format.prettyPrint(), - format.json(), - consoleFormat - ), - }), - new transports.File({ - format: format.combine(format.json()), - filename: "app.log", - }), - ], - }); - } - /** - * Logs an informational message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - info(config) { - this.logger.info(config.message, { - service: config.service, - method: config.method, - details: config.details, - }); - } - - /** - * Logs a warning message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - warn(config) { - this.logger.warn(config.message, { - service: config.service, - method: config.method, - details: config.details, - }); - } - - /** - * Logs an error message. - * @param {Object} config - The configuration object. - * @param {string} config.message - The message to log. - * @param {string} config.service - The service name. - * @param {string} config.method - The method name. - * @param {Object} config.details - Additional details. - */ - error(config) { - this.logger.error(config.message, { - service: config.service, - method: config.method, - details: config.details, - stack: config.stack, - }); - } -} - -const logger = new Logger(); -export { Logger }; - -export default logger; diff --git a/Server/utils/utils.js b/Server/utils/utils.js deleted file mode 100644 index 5bd9258f9..000000000 --- a/Server/utils/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Converts a request body parameter to a boolean. - * @param {string | boolean} value - * @returns {boolean} - */ -const ParseBoolean = (value) => { - if (value === true || value === "true") { - return true; - } else if ( - value === false || - value === "false" || - value === null || - value === undefined - ) { - return false; - } -}; - -const getTokenFromHeaders = (headers) => { - const authorizationHeader = headers.authorization; - if (!authorizationHeader) throw new Error("No auth headers"); - - const parts = authorizationHeader.split(" "); - if (parts.length !== 2 || parts[0] !== "Bearer") - throw new Error("Invalid auth headers"); - - return parts[1]; -}; - -const tokenType = Object.freeze({ - ACCESS_TOKEN: "Access token", - REFRESH_TOKEN: "Refresh token", -}); - -export { ParseBoolean, getTokenFromHeaders, tokenType }; diff --git a/Server/validation/joi.js b/Server/validation/joi.js deleted file mode 100644 index 0967d9150..000000000 --- a/Server/validation/joi.js +++ /dev/null @@ -1,605 +0,0 @@ -import joi from "joi"; - -//**************************************** -// Custom Validators -//**************************************** - -const roleValidatior = (role) => (value, helpers) => { - const hasRole = role.some((role) => value.includes(role)); - if (!hasRole) { - throw new joi.ValidationError( - `You do not have the required authorization. Required roles: ${role.join(", ")}` - ); - } - return value; -}; - -//**************************************** -// Auth -//**************************************** - -const passwordPattern = - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/; - -const loginValidation = joi.object({ - email: joi - .string() - .email() - .required() - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }), - password: joi.string().min(8).required().pattern(passwordPattern), -}); - -const registrationBodyValidation = joi.object({ - firstName: joi - .string() - .required() - .pattern(/^[A-Za-z]+$/), - lastName: joi - .string() - .required() - .pattern(/^[A-Za-z]+$/), - email: joi - .string() - .email() - .required() - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }), - password: joi.string().min(8).required().pattern(passwordPattern), - profileImage: joi.any(), - role: joi - .array() - .items(joi.string().valid("superadmin", "admin", "user", "demo")) - .min(1) - .required(), - teamId: joi.string().allow("").required(), - inviteToken: joi.string().allow("").required(), -}); - -const editUserParamValidation = joi.object({ - userId: joi.string().required(), -}); - -const editUserBodyValidation = joi.object({ - firstName: joi.string().pattern(/^[A-Za-z]+$/), - lastName: joi.string().pattern(/^[A-Za-z]+$/), - profileImage: joi.any(), - newPassword: joi.string().min(8).pattern(passwordPattern), - password: joi.string().min(8).pattern(passwordPattern), - deleteProfileImage: joi.boolean(), - role: joi.array(), -}); - -const recoveryValidation = joi.object({ - email: joi - .string() - .email({ tlds: { allow: false } }) - .required(), -}); - -const recoveryTokenValidation = joi.object({ - recoveryToken: joi.string().required(), -}); - -const newPasswordValidation = joi.object({ - recoveryToken: joi.string().required(), - password: joi.string().min(8).required().pattern(passwordPattern), - confirm: joi.string(), -}); - -const deleteUserParamValidation = joi.object({ - email: joi.string().email().required(), -}); - -const inviteRoleValidation = joi.object({ - roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(), -}); - -const inviteBodyValidation = joi.object({ - email: joi.string().trim().email().required().messages({ - "string.empty": "Email is required", - "string.email": "Must be a valid email address", - }), - role: joi.array().required(), - teamId: joi.string().required(), -}); - -const inviteVerificationBodyValidation = joi.object({ - token: joi.string().required(), -}); - -//**************************************** -// Monitors -//**************************************** - -const getMonitorByIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const getMonitorByIdQueryValidation = joi.object({ - status: joi.boolean(), - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), - numToDisplay: joi.number(), - normalize: joi.boolean(), -}); - -const getMonitorsByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), -}); - -const getMonitorsByTeamIdQueryValidation = joi.object({ - limit: joi.number(), - type: joi - .alternatives() - .try( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"), - joi - .array() - .items( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port") - ) - ), - page: joi.number(), - rowsPerPage: joi.number(), - filter: joi.string(), - field: joi.string(), - order: joi.string().valid("asc", "desc"), -}); - -const getMonitorStatsByIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); -const getMonitorStatsByIdQueryValidation = joi.object({ - status: joi.string(), - limit: joi.number(), - sortOrder: joi.string().valid("asc", "desc"), - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), - numToDisplay: joi.number(), - normalize: joi.boolean(), -}); - -const getCertificateParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const createMonitorBodyValidation = joi.object({ - _id: joi.string(), - userId: joi.string().required(), - teamId: joi.string().required(), - name: joi.string().required(), - description: joi.string().required(), - type: joi.string().required(), - url: joi.string().required(), - port: joi.number(), - isActive: joi.boolean(), - interval: joi.number(), - thresholds: joi.object().keys({ - usage_cpu: joi.number(), - usage_memory: joi.number(), - usage_disk: joi.number(), - usage_temperature: joi.number(), - }), - notifications: joi.array().items(joi.object()), - secret: joi.string(), - jsonPath: joi.string().allow(""), - expectedValue: joi.string().allow(""), - matchMethod: joi.string(), -}); - -const editMonitorBodyValidation = joi.object({ - name: joi.string(), - description: joi.string(), - interval: joi.number(), - notifications: joi.array().items(joi.object()), - secret: joi.string(), - jsonPath: joi.string().allow(""), - expectedValue: joi.string().allow(""), - matchMethod: joi.string(), -}); - -const pauseMonitorParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const getMonitorURLByQueryValidation = joi.object({ - monitorURL: joi.string().uri().required(), -}); - -const getHardwareDetailsByIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const getHardwareDetailsByIdQueryValidation = joi.object({ - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), -}); - -//**************************************** -// Alerts -//**************************************** - -const createAlertParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const createAlertBodyValidation = joi.object({ - checkId: joi.string().required(), - monitorId: joi.string().required(), - userId: joi.string().required(), - status: joi.boolean(), - message: joi.string(), - notifiedStatus: joi.boolean(), - acknowledgeStatus: joi.boolean(), -}); - -const getAlertsByUserIdParamValidation = joi.object({ - userId: joi.string().required(), -}); - -const getAlertsByMonitorIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const getAlertByIdParamValidation = joi.object({ - alertId: joi.string().required(), -}); - -const editAlertParamValidation = joi.object({ - alertId: joi.string().required(), -}); - -const editAlertBodyValidation = joi.object({ - status: joi.boolean(), - message: joi.string(), - notifiedStatus: joi.boolean(), - acknowledgeStatus: joi.boolean(), -}); - -const deleteAlertParamValidation = joi.object({ - alertId: joi.string().required(), -}); - -//**************************************** -// Checks -//**************************************** - -const createCheckParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const createCheckBodyValidation = joi.object({ - monitorId: joi.string().required(), - status: joi.boolean().required(), - responseTime: joi.number().required(), - statusCode: joi.number().required(), - message: joi.string().required(), -}); - -const getChecksParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const getChecksQueryValidation = joi.object({ - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), - filter: joi.string().valid("all", "down", "resolve"), - page: joi.number(), - rowsPerPage: joi.number(), - status: joi.boolean(), -}); - -const getTeamChecksParamValidation = joi.object({ - teamId: joi.string().required(), -}); - -const getTeamChecksQueryValidation = joi.object({ - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("hour", "day", "week", "month", "all"), - filter: joi.string().valid("all", "down", "resolve"), - page: joi.number(), - rowsPerPage: joi.number(), - status: joi.boolean(), -}); - -const deleteChecksParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const deleteChecksByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), -}); - -const updateChecksTTLBodyValidation = joi.object({ - ttl: joi.number().required(), -}); - -//**************************************** -// PageSpeedCheckValidation -//**************************************** - -const getPageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -//Validation schema for the monitorId parameter -const createPageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -//Validation schema for the monitorId body -const createPageSpeedCheckBodyValidation = joi.object({ - url: joi.string().required(), -}); - -const deletePageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -//**************************************** -// MaintenanceWindowValidation -//**************************************** - -const createMaintenanceWindowBodyValidation = joi.object({ - monitors: joi.array().items(joi.string()).required(), - name: joi.string().required(), - active: joi.boolean(), - start: joi.date().required(), - end: joi.date().required(), - repeat: joi.number().required(), - expiry: joi.date(), -}); - -const getMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), -}); - -const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({ - active: joi.boolean(), - page: joi.number(), - rowsPerPage: joi.number(), - field: joi.string(), - order: joi.string().valid("asc", "desc"), -}); - -const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({ - monitorId: joi.string().required(), -}); - -const deleteMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), -}); - -const editMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), -}); - -const editMaintenanceByIdWindowBodyValidation = joi.object({ - active: joi.boolean(), - name: joi.string(), - repeat: joi.number(), - start: joi.date(), - end: joi.date(), - expiry: joi.date(), - monitors: joi.array(), -}); - -//**************************************** -// SettingsValidation -//**************************************** -const updateAppSettingsBodyValidation = joi.object({ - apiBaseUrl: joi.string().allow(""), - logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), - clientHost: joi.string().allow(""), - dbType: joi.string().allow(""), - dbConnectionString: joi.string().allow(""), - redisHost: joi.string().allow(""), - redisPort: joi.number().allow(null, ""), - jwtTTL: joi.string().allow(""), - pagespeedApiKey: joi.string().allow(""), - systemEmailHost: joi.string().allow(""), - systemEmailPort: joi.number().allow(""), - systemEmailAddress: joi.string().allow(""), - systemEmailPassword: joi.string().allow(""), -}); - -//**************************************** -// Status Page Validation -//**************************************** - -const getStatusPageParamValidation = joi.object({ - url: joi.string().required(), -}); - -const getStatusPageQueryValidation = joi.object({ - type: joi.string().valid("uptime", "distributed").required(), -}); - -const createStatusPageBodyValidation = joi.object({ - userId: joi.string().required(), - teamId: joi.string().required(), - type: joi.string().valid("uptime", "distributed").required(), - companyName: joi.string().required(), - url: joi.string().required(), - timezone: joi.string().optional(), - color: joi.string().optional(), - monitors: joi - .array() - .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) - .required() - .messages({ - "string.pattern.base": "Must be a valid monitor ID", - "array.base": "Monitors must be an array", - "array.empty": "At least one monitor is required", - "any.required": "Monitors are required", - }), - isPublished: joi.boolean(), - showCharts: joi.boolean().optional(), - showUptimePercentage: joi.boolean(), -}); - -const imageValidation = joi - .object({ - fieldname: joi.string().required(), - originalname: joi.string().required(), - encoding: joi.string().required(), - mimetype: joi - .string() - .valid("image/jpeg", "image/png", "image/jpg") - .required() - .messages({ - "string.valid": "File must be a valid image (jpeg, jpg, or png)", - }), - size: joi.number().max(3145728).required().messages({ - "number.max": "File size must be less than 3MB", - }), - buffer: joi.binary().required(), - destination: joi.string(), - filename: joi.string(), - path: joi.string(), - }) - .messages({ - "any.required": "Image file is required", - }); - - const webhookConfigValidation = joi.object({ - webhookUrl: joi.string().uri() - .when('$platform', { - switch: [ - { - is: 'telegram', - then: joi.optional() - }, - { - is: 'discord', - then: joi.required().messages({ - 'string.empty': 'Discord webhook URL is required', - 'string.uri': 'Discord webhook URL must be a valid URL', - 'any.required': 'Discord webhook URL is required' - }) - }, - { - is: 'slack', - then: joi.required().messages({ - 'string.empty': 'Slack webhook URL is required', - 'string.uri': 'Slack webhook URL must be a valid URL', - 'any.required': 'Slack webhook URL is required' - }) - } - ] - }), - botToken: joi.string() - .when('$platform', { - is: 'telegram', - then: joi.required().messages({ - 'string.empty': 'Telegram bot token is required', - 'any.required': 'Telegram bot token is required' - }), - otherwise: joi.optional() - }), - chatId: joi.string() - .when('$platform', { - is: 'telegram', - then: joi.required().messages({ - 'string.empty': 'Telegram chat ID is required', - 'any.required': 'Telegram chat ID is required' - }), - otherwise: joi.optional() - }) - }).required(); - - const triggerNotificationBodyValidation = joi.object({ - monitorId: joi.string().required().messages({ - 'string.empty': 'Monitor ID is required', - 'any.required': 'Monitor ID is required' - }), - type: joi.string().valid('webhook').required().messages({ - 'string.empty': 'Notification type is required', - 'any.required': 'Notification type is required', - 'any.only': 'Notification type must be webhook' - }), - platform: joi.string().valid('telegram', 'discord', 'slack').required().messages({ - 'string.empty': 'Platform type is required', - 'any.required': 'Platform type is required', - 'any.only': 'Platform must be telegram, discord, or slack' - }), - config: webhookConfigValidation.required().messages({ - 'any.required': 'Webhook configuration is required' - }) - }); - -export { - roleValidatior, - loginValidation, - registrationBodyValidation, - recoveryValidation, - recoveryTokenValidation, - newPasswordValidation, - inviteRoleValidation, - inviteBodyValidation, - inviteVerificationBodyValidation, - createMonitorBodyValidation, - getMonitorByIdParamValidation, - getMonitorByIdQueryValidation, - getMonitorsByTeamIdParamValidation, - getMonitorsByTeamIdQueryValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, - getHardwareDetailsByIdParamValidation, - getHardwareDetailsByIdQueryValidation, - getCertificateParamValidation, - editMonitorBodyValidation, - pauseMonitorParamValidation, - getMonitorURLByQueryValidation, - editUserParamValidation, - editUserBodyValidation, - createAlertParamValidation, - createAlertBodyValidation, - getAlertsByUserIdParamValidation, - getAlertsByMonitorIdParamValidation, - getAlertByIdParamValidation, - editAlertParamValidation, - editAlertBodyValidation, - deleteAlertParamValidation, - createCheckParamValidation, - createCheckBodyValidation, - getChecksParamValidation, - getChecksQueryValidation, - getTeamChecksParamValidation, - getTeamChecksQueryValidation, - deleteChecksParamValidation, - deleteChecksByTeamIdParamValidation, - updateChecksTTLBodyValidation, - deleteUserParamValidation, - getPageSpeedCheckParamValidation, - createPageSpeedCheckParamValidation, - deletePageSpeedCheckParamValidation, - createPageSpeedCheckBodyValidation, - createMaintenanceWindowBodyValidation, - getMaintenanceWindowByIdParamValidation, - getMaintenanceWindowsByTeamIdQueryValidation, - getMaintenanceWindowsByMonitorIdParamValidation, - deleteMaintenanceWindowByIdParamValidation, - editMaintenanceWindowByIdParamValidation, - editMaintenanceByIdWindowBodyValidation, - updateAppSettingsBodyValidation, - createStatusPageBodyValidation, - getStatusPageParamValidation, - getStatusPageQueryValidation, - imageValidation, - triggerNotificationBodyValidation, - webhookConfigValidation, -}; diff --git a/docs/diagrams/Bluewave Uptime Auth Flow.pdf b/docs/diagrams/Bluewave Uptime Auth Flow.pdf deleted file mode 100644 index d7c7b45ca994eeecfe65a48338f080370828a555..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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

ohz&NPaK5!zZi5(SX~ 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 QTt)^421Z<}s;>TS0L{;|o&W#< diff --git a/docs/diagrams/Bluewave Uptime Auth Flow.png b/docs/diagrams/Bluewave Uptime Auth Flow.png deleted file mode 100644 index 32498b3b49f4ed948c6bad3041311803ea4bc8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47938 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%0DO>uZw~9 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 diff --git a/docs/diagrams/Bluewave Uptime High Level Overview.png b/docs/diagrams/Bluewave Uptime High Level Overview.png deleted file mode 100644 index 65a2866b7d7d0a82fc2de2484867fbba22b6d523..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 193734 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�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

`fM0~ESs92(yimdT)i9oz-Z~$&| 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

WoH3`v6FO1h_^CusCuZ1^dkX^zT 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

ej*ReF878zy+x_1R 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۷&Ky6@{{O5BEG+7oh|S zCd$$Q$gzn);rNW6*4x!yn?^;CD-ob%1->p(O#xQEBys`-FW|sNez#K*933C$?0{W$ zo9&PU>~4{TIstu1rshDbq7q!abo z+||+Pe`34}mc=i`vQ*TSMoDt1ZPDon14EoU5!GPaaYeb^r#D!ktynLW791Q5R#QpZ z5+3|8{50QIC2<}AjUILAcQPVn+>b9L2zK{(qB<7d99<$k{jVbZomYnc+bVILKQC{4 zp%m1b1p0A6IYPgHJhhfZ0XwosU%#HM@MmQOXqLSX)PqVIUhbohijC#Cs|-y5J!xnd z$n)}^JLgGvM)oUfQjetQ=qEKDdF8kUVn!uP51|kanvQcJXjjtRhj7ms3SO~Ou>5ZT zhbRXj*2ws{uo!KR3LFB?a7Vo%5yjn~psZ5>(CpzbhkmaZg+N&pqbzE#JcN<+;ot5D z{aRXDI^mRgPbN))54l~pjqHK3*G0j?(EzO>_?WyznnfW#mSaT}Y%4?{{y>{wp-U#= zP|pKCUIiu(+zm7>U?UE#wKS=B&?Z1~v(Etsa290MvW z#kPkJ9<)bg6l{^ErhMzmHfqbJo0cd#L-zgQ&#y4ePDK01#xZcKAA-YYIVXioFyS~; zv*T|haC=u%^8^f_;SJD3_4Wf1)y732tZ;H4Y>x|fz5u)8Q(DP#_hNzI*&GWQ{@>yT zH6Iskt*oq~DTiQRp}L;48}w}IX?LkTcyI$eRpHKg2)pu_?kmBN?qm0`C=~$1haDgH zG(SC#8Y`&XlfTrVrH^9ZyLX$XGjvN`oY)R*fI5>7Rr;uQK_@!k)?LDtnc;% zf(oUcQ=))F4Q$A+2`LuG-_I9q6NhbW+Gt_}!8#|MVH<*sT6tHiDQg3ZtKPj47O~|3q$Tic@bHhCE$!ZYel5_>RlDr9Z0ONZR z>S$i6sZdUU8y#1Kf>X&j%v0*4CaP!<_mMVP3vm-&dhF z*49J#xClQKX8^g-;jj=>Y5!bdA@nOwIn503Y;yTCAZ(Jo9mLPx$CnF(288@!zhmZQ zR>KvGwtW_*G@@W|T3d69nuTzHMEe|`%JeZ6`j1r4l*MPVWmX2e``o5dVfyPNm}wu+ zcpRfsQ{+{}4i(`mn}i~wP=On`f>5gpMwI4AEkhl*`H}Fz>rfcmsn_HM=z~SUXu#_F z0GS2$viW>B3im*U`4|k{1V&|NXMeYQ>L8cuO2cFWN)54bY%?PROg?vrR9K2T$#}d*_xmJN{KIY4v6iw-4B~Sl| zqQ$in_01CL31CEqAdI@b%Qa*kMVXSDP>`0Bm#=+rCR?-;GIbd-TX z*l7#p7U0=X6BAssdX}N$BB%~$H0(Sm@irXk40%^9K=h)Z>;Pr78~Qy>QiS4#2Iudq zg_Ba16vm=|W(UUCVLIyr?UJn7)FmzP)t-;t-$7KeW>G2_WY(j)zE601!tn-gkHTWq zO8^sZD~mt|r8V-%>_43y)tJz?S_QRx7F{fHONxIoXYR zueQQ){09jElc2fjotKaTXI!!$SUUV+?^Z%NO%G(E@piFFVJG6k>@{Hs@kV*BMN>{Y z?L}cZmBHR_ecWf@)nh|%prZr*u|x*d(zyyI*q`Ev8gc4fLiajK?(SC37B(tS1dmg9 zA(s39dEG38<8lTu<>bD)+hhG~9{FEopm)XK@%Qf(E)VCpr$DfZ#{++djEoG-j0xm5^yk;rC8hp5x$;!AK^lrKvJidYXp?!YsrI2=}Z=Bsm$0)!*(Z_C5ix zL#7Ic-f*?F<_)-EhB5?AJHA!|=D1AftrO;HgaAd+!T)*``T&SO4u)5$5n+`xoEFAB zjdfOfJUx+2BYC5doIOz%VEaIckxx&?*%|Umx_1ZIBJ8_O@OY4=7J_4O%1p-a@o#C6 zd&T3^TxU!bw@)SrTFW=`!V|J6S?V~(nw0~EMfQp)dv($F0u>G`pA8|& zHw*-~_(~|5ehlAg_IGO=a*cqXJw?n9wR>Xpcc$kZ{xgLTiU*?wvQqHjbKtOfeLX$t zl|;TYo1;RkwvN#Z#k{`1|W*)s9DC-Mm-CQ5?Dmri4Gy9FA#`iv-28{ePRizk}Z0)W@>V$*jXx$Pwuc z@q%ud?G;;y73t{a&>5u5;JM!e2e+JL6lSNY*LUAoG|KHz5g`}5`ja&{IR*lck^xUo zRNQX1Qx+L~X&X2Mkwo0}HafcNuMfyOWHz*++!Cb+aJ#~^Qmem@5z{R8iV6!0OQI^U zFy^-X>o!53Rk}~J*{19?Br7Prkb%$xA{L>1rJ3<# z6ypAW=KtAup=RKfS_T(3$s9JDIb>pBFlz6f44WE)2p=#P_4e-d*!7-)6Ry)hPHiwo*hxz`*d*wrME;yDkS$9ssTZ z;oxtaOB(7F@zA4{fVvod4O}~lZ9;luEmnpL*F(T8Z-+;GkmSs~HUgT^As>N;o4dk5 zgR-Su3-`?@}mSC<9Ae|{h-Ed$A@W?NX%`J76m5{{>>rK`T;tN@As+3+%` zGSGzM;7;g=ZTg+YW8zS^1L|WM`tXp0j|1mdl0VJ2e%U|sNBgC(!eH{ z7ec->w>s=E2l*C=q?HE?nu6!OjAERA9BA?C0df1%@&oZ*h2ZMYuZjl0tn)|&TF%Ue z6zv}P8RutNK~z@AWto^{EmIDmNB{4&BS(&$E#yLFL#wHjyx5|vYEV%{$3Q`sbZ@9L zr}9ey@;rNMl^_Q4*pUp{(%Phl5N4-<=OBLuMLtsS*DWn@Z;+;)L5QJply=~XP#A;S zGm9=78X73F$bb|uJ~8nP@*!{m*yW6TAm6*_40g{8`XWzUq;lE`FOcf*qK(N=xfjoV zFWiIm!k7jwK~hx&g?%*e9^N9KPzqpjULoDheWjk$RAE}dqnH1v;JPBT8G*Pep@VGdzC70(58Nx3GHM8kGtiWRV2E~GKkdTee zTjrY#h8=r=S?qE&pUF*lbhkrAGWwm`X!DJtAQzZ}2)qJnP|;|QEPx`iKT1!k0Dj!nrXm(_Pwf?Z9$7~JS-zUygW?THG%Y&{!4}N{i=80EyPYG>5NBInnhxTJ zegeuiv;0J%DOJ7!!Aj3Wq5B@1TXnitzm6&r#33w%URa)1E$qG_D1=SX+Epnsa|w1p zbqnDiqLnV9UUAF)E|s1(iA=m?jE>*m~gsS z*9!fX&tS?s#8vwaVL0IDKzp#bdI2p5T1wy7z5n*)QCW8S`T~&UsIr2k@oM;^f|^d< zXI;y~2W1FPPmzt;pCKjMs5%C%MwC_Qblevnf>VZWW+*_*0{JaRI0QqmiG3Tw#N{AC z7@GZg(2{>|2%<(3A#W_`L;WBFnstl0c1zP;0&t$g4BI>2+f8+#cc9cVS+`6InvwK! zC#_^o+IK#N(nwQOhUMgE$kLW73S-velc11}2_em1AZDy~8mLNnqIeF~kyesHc(&LIMME$@&m8 z{6dPOzKROr0u*Kuf12LuYj77l`wMa_aua6wXPiU2OB4Pwf}NH1GlEAW?=(sd;3#J2 zyIU(%q-EGSjF4i$LQH>*G~-wYZESt->os=%1p@wo&Z*NAjx))8RHugiJ9vYhg5;lB zEgZuClPWdapLJ#miYGFE+CU15D$hUfA4KB*Uo4yd?+yK7H~;sM{jnVX*IFDzLkjk(MOA3`+b7 zK{J`#3(+l>=Dz*dr}w@5^gL{=oN|+Xq0H&g7u}7W=Ca_>dYu|eybJ<27k+hJq<*G4 zq;tf$7M3MFX$Gz-G8un{zLv%9iB)dxj+ySjaW z!_1Pjx3k*2d223N|Zb@}-Eou_8X=WQBT!e?42jM)h{ zp6Aw-oTKQPpRtiU3k04fswKEH&&<>dp4vT>A|#e)&Z+4fgTSlnqOYVU?rLPpyx4Z{ z_*?ijb;eE;P0iF}=cDa&kNz`&c4(ME)w>qd+Hcx5K$TL?6oolHvgp zbN7cb=&4S!25OV~pzD2w%LX3bQFwgD)Nt!V1l<8u%e4tU{o#s;4Q{lD;Z4)*QtM_4 z-0S`f!$S&bKDCwFMNt%M#;3qvDPjeOEb!+Sk zzOfh%$12WE+td!hLiA3ENs|UgCLGKL%mQmzYNk%TRs8q5wXP}Ld=xhYK$~#qjd#bT z@xgnra{-eV9^@4r`PN`R^lW(8NO)kPrL(3 z{wp$O>#xX?4qN<%w$=6a=P9P2pzBniy&yC(#rZ4xp!w9PeQ(Xta}D6tx)Ay6;;`Kz zV}*Yo&$uQH#!dsY;d+k-RydT|JeJs~8d_~nBYG)IZC>s%pu~8&(@I>rD1vpcD_1YQa(!d4(7@!VY28 z5jfowlO7^6$t71Y=)rnyeFg{r;fjnS^I4k&+&2WMFQyx)u@{}@`1gu%g2Bps3Lv0z zw{<&DbX5{_$jnoSmO|jl6wki4F_^7J(FAiHZ4F zTDt$*#uTU6{(pxU{D1A|woT@L{`J41$$ypt{o(&Aya9S~`apsrPZC|th&(DBs)*eEBBdek0)ZcfzzDcgXj zwSdh^Z>uR@Z2ND0WVA{b$N@4D{c>`F7YqJ2_T()pC?)o0G7PDemB#f4a`Za3CQtMx zsGt)FZ~5mf*o!0Q4Mi5q%F*G}#JmkT>CltNA|X4PtFUYNFArvWV;Px;>eg-#|LQNu zaQUUM$@G_Y&pG)$seta>?VdxZHjcGkd2>HlRTKkF9YST*vF|a>@0c+x!C~{ zNuCc7?pEM|I9S&EbEE-m_0OA0?*RyqeekCwRkVs>>O$zvU^@+qE^5}!VoO^|u2?d2)<4J0hs;0NW7L5%s&_jabBFyPlZ5&QPDGjQuKU;F#Va{S-6zxDS4!!}{2%OnXH-*Z)Gm%Q z;@Gi)AfPacio&4Mn~sQurgVXT5&@-(^w1m`1*F*!rK%`MOQe@jEI_174IL%)&_W;q z0_5%&#c|g6>#lXz{q<$7nQ`!Na?V@!e)jY3{p^49vYtOv0+tDq5FtNuO#9p4xBvR@ z0o1hwl;+6AwHSJ&8c^Bt$L{vOB+2o{xbFEnfUr;kW<}h-{P(w{PX5cQM82@3w&X!= zAs3e;RNO#f+wwIiS9PVw?k!7qMn1QY&`T)dH~`fw9sS?FC+qir&t=~73+nr9Z?vkt z7Z9*seAX2uwW80pV?Vvi@8}w~!gwS+58kYY$7=*&^|!6|yyjDd>kl*id1te27jM{M z9|a#JW@td`PTg2i1UC9RYxd# z{Plaff;ZELA_$2#ZOg6ncp4OiKrimky-bS7%PSC(-I3|jr%?~{k*=FuII8S^U|S|>QJ~# z(0leeWb^VjKp9>1)XYmB=Z1Gf^+kJrjN|o{cX_u-)Yse2M8feVu2{|9ep)`~&wFcj z3vzLJzbDoaH?FLY%l@hE z!{JXAER0lasVZ|tDdndLv1TFBu45Sxt*!63pY6?IyUj1x1Fbv_2k{5Tc0U~|o)Y{j zp>8qkcZ1L!+TT~it)ibU5!8r*Q?J{VPyu!L$2~ZRt;mCY`@ChfU%>Fg2x32kV*$o~ z|MC6b{(bFy$N9(0)B7U@_R^jeHgNR5sbWbaN=;t=+&cUtzxAlp&C(l~m5kWS;`>*A zp7`r|0-emZ{6|0;1g2!g-d6ZG%8{dVpD^`8Fr zYyZn(ejn=Z|M`FCIf2yR;Lt`}A8N9By$*{{JYQZy`+i0Z92bJX7}}}oHH%1O^!2V0 zH2z*c4N>sV4~8!LoPerUQau)H_LGO9NbEsJexoZ>Z)v{$P3l5kg$vX{hkgJ#ChvC7 zG*~R98Lxv-h1+b6gJ2-k>P|FK?AoS0QgL<~UXxcWQa$3A=&9dJoK=06-qT)x+-%cA zx7E_lbg@xjb*@iecpVX62l4sn*HbRJE7o@TskQ>3l}ZCSeC+s`TN%`ctCEMoVvIwD z+}Pa%%Owd=5b%f!sq@#a^dGJQ%ZN&$&oCFP51V8;h0?C{pP@a9s|hqPz;}-qWZSfM z6v&q>R8~G!a)R{k$BM+mkZc_~3-;qb!_dCvibZRXExZOC_9%5&WSzY-VM-(VkB0>W zjD$p)w{KKV*qw%>2cvp~5p7!p*m-ocNccXfl$U5gTt}(1XX>qc@ zs1%CU1E1ggUY5)^`lJ&~aPTf~pHy#ponu)kxF1DPP5GI=WvAf-)%TYFMXJw3=1>N? zYZ}xYo~FHZq6}vYW@0Kg-)XS+)+-I|mbPe0g3#7W*M@+_N%!j{#X)8xN*|GfU+oH5+RFh_6j>0uCM z(8;%XKz^(AEAaR{vgYpQZBQMEEmI?0C1$NjgX&MX@G3F5s0E^jT@Id5!*5CS`uTRZ zq#(^Z05%0FfL@-}cHco~zlp3AR7{>dOuXK5B;7)-gKDBcn0{{?@zx-PZ@X_VR9O{` z-S5>gpUYHRuBTA?Pf&78Y9ESC?LOWe36b!q%tH9#scRQg7#4wTMAKnZvQr~%A}FjT zhIbKbrafwNGM{(pacIV(S~XNI-|bGXOCQOedV}xY4rnP@=?pVCzPLdcCc^p#5f(3{ z-#}lrEwiejD%O4enaslHQ;qbwInFaAIAF9N>|a>$<5Sesi52rKt|vjIaNFgzxd5FE zSp#vDG`0!1O}G0o1Q$9xHS5#91RUVK;ng=NxVs4FBZ+FK7!uSLR8L2XqqdMx9nH%# zf?++1&b~plHc+-qxzjH$X_E@~edg{}A0I}7N_5NPu^LJ8_8@kEbO?G|vFj*M%N(*q z;_vuULIBsSYSB-cd9oQK8qz1+dKPWaR#IRKR#85zFD~lBX^Qb~fg)IkmZz^<*mW*c zzSP(I>Pnhq6zE`k92*m+=_TJm1}aEJwxei1NBRk>ME&iImj|(9JT%94PJywg%#xaS zSZ8#~c;V>gpJw?o6CCazsNy-=L-U-8;@2q|&)LsiLdVHaD~n_pX!LxyR}tZ0Y>F>> zS#Q|=xkN8C{YL%7Ru;iKT6Zly&&M0fs5!P3s}JZG-J@u19L$<|F=!U#d5yZ6&sSg4 zXmNx(;A88^-R!6yDc*mIp+pyVr5}Me*m}q`mQKM=K++Vw(R}N3OPKFDXKFKH;XZXE z+ohushqbVE)wnuXwBmZFDEMjM9AoU!ix&7bWCPIsvDHI#q*F@S#W%EqA zQ`h`G!@MCutSM7YU*D(m%Z#A5*Lf(HB~;^A9F#G#bL%D4 z(%A6J8^?3u9V`{uye#HJUS^YvS>6Hjk;fH*CtSzUqo<1w&Ww1u^pCj1Kl@2H-5`Z- z5Z0z7D&5VAF&I{qbI;VV>&!X`hb%vRJbA8IJU@w489LqAt7$=(q*b7_u}#d1SSz4( ze5bg_Q7+qP0DB-KF}%qo5Dx{JK3wN7(}wE6cObm zva^gcY%3&VQFLK=A9o0zd?_C)n$j2A_FV+$muKGFH;qMg<%EG+`wTzZLmN5_Bl>i2 z>-Hy6OgAjYeW4EVz90Imozt24zG)3D#@#EOW-&DoHL&O(*YaX5*`#9`m z2Hpd9kq`03=o2(Y5;J$>(7t;Ki*docWh0bYnI&^8)k5tXs5K;W-$%+PG^07bK|& z3Q~)Yw6;;2c(JlJ16lek^V^uc%@Xr3&IojTeW#SA+bj}i`2#efsk*#bICaBuYbcis z1~nEWHz8lG))L(V` zGL~;;dR^{~!o+w-Uk!CIX`z7Mu51;+0Tgb;R)iTwp%+Y2x`TJkeg ziGhcXkp2l`7*P0rHuw^}XhitSvB20fsv&b+%TSF-hQIViVq^Tb|q?xoS{DEM=P zV_^ObbDSNc#(#}lcOdch()}Y2i-QIKf_gL!qgR$`#2f;Y6kb#q?Q|~~owJQ-c!h9V z)4@mEszBTW=RXqb$-)c|gAX2Y*<1@C3Sk+TdM$Axs3N$|nuryC=?I*Iz|PLO3-C|F z-va=sLLu05aPBHoi-lnK>&N1@FBG><7c(5LFW_s4Yf25=$FfI3INcT`3G7?)j~Exo z*w3|vNFeP2*d1a5nQ6zI_G^D_9e@nuC9cPc;LFws`l2lfTaCwgelibc0(^K=s$8X+ zS=8DST7^>$YvkqYh^(B_3_U_x+j#L{my0p35TOVV2_lU-S)K-zGcAYQjYQl?R@Y;A zNG(oUog%9`xV$9iPLNN;+L#_0@AL70FMw`OFHEHLY-<%ERXwmgKN({hD zRY~&3=TbX{VCbw86Qy+xq*M5A>VPx4|J1CouR@iK$#OquUYvf}32@W!^SY?g zPqXXo{+MNGXtdkzCW9Uz|a2Gb!C2>uLlJUXFqLLYWrwfM}KbmpV#d` zJR;&L`^(45;JWLWTl_^4QiG(Zxah535bQLiSfn^oi`!FjP>?b z?U%x7K#jkKLJOdj{bAk!-Vzes`aHCm(bP4a++s$ZSKSqMh~YcRi1@1_337I|BCngIet1MnzXh(Y` z3@CzUV@Fp;2L5|X@&4PR_aA)6ho$!-Oew zM%iKHZuGH>(g#eK9`#SbZu}8z#V5UQ^<1`Jmt8$5A&?p^>PS!1DZSNB=X(d8CR+Eg z*F1lX;!(i`Y8Vzgz$3HTpI(~de&F)KL$Q>uLogV`GJtsHP-b3I^|4T67a)tn9yhNG zsmuXrDorO%U%$xN(~kUPMC>Vo+{qd+H^C2ti^C0{tlJX?(os9bjG|4KN*n7+!Fe^% zC5>|v1eM2{2-5C_A=z@$R=k!h@QZedq36b)-z*JvxrsjlU49XNhm1xQ$p;Er=AR+v zZ>ELFDA*-2f$xayBu_<{cE2FGmF_yFsn+D(v$(m*vlg^7raxi99c}4}zA(>d+Q`Ut zD50=yYYoS$(`~EKSR|8MN>{HJtiVZIT?_pV&OqpbK3;sJRw3~!$!)pU%=gilEP?Qr zp8@ONaX@yTBZ%~%5Lg%<&o*K^7Z=itD)-Ol27$Enq4%svJuD!Et#lw5tr_V( ztS^52cxAX=Wvy(p1OhAYaL1qFY=K~+v<;e_jiUm**7_%$Ts$}Ujcf!Rnry!a0RFc6 zAxTEsErm2ozpt$=lbU5_=2MTa2U?*0gVe%`^OZMZ!Sp2%`T>9*0M70uYZ`_jkt&bd_<>HoIpA(7=SI8$cOs&V(iay$b2vc-e4N^3q zq*epz2iaU)7cb4#i%q=kFjDH7n$zrJ*9~@aF}v6&5O``+Y6h|xR`J4(saGoRTwoPe zFZN^57#kIq++8Z$nC=~$eJVL=Q}tk& z0&!-IJGUB!1mVY4WqEv&y=o^tMK(>~VI2}CfG8A2rsi|MAx*u-Bd>lB!O z_4a(JWqb${KW%kI`{?wFd>(ZCC@5(840|e{H;Y2M;Xzs0#N*If1fszzYX#eM-MAUot+ijO6*@&2Qq?#vmm^b~%+V<~?TC=^YIIj|`$BgLn?e6Zh zheI$_j^k}H(S;Je~@iprQy05hVPR)v#2!-kJg{Zvv1Kg}-M?b9|jNeml$J zH-5V0*4~L9vPg1I@S~tL zSqIK`=)NPSMcycDMRXR<2Teci=|i*+k!~L7B-N+vAn3)#d+zrscYrV|{_PYT7k(Ol zw@CQ7S^jwC6NT+;5DY|tFm04B{)dmxsQM%P!k`pA)4!{6iRBiI4I6$`Uvl!N<#tPb zt!SnW8cni^r?;HKC0Iexo{VP*VCSe+efk&_q2jZ&7oj}ch=&jQ#fu6X8C@_0J~AHK z5c?_%389h9@GiKHl&J(YW4D7uvzoY#=Hz66xM1wlx-8?-P+kcy!PFY4G4g_w@mqMi3(KB&eep-sMT>2=`lCYefqkR?* zg9@&}8V24@t!eQ?ph&FGe*VJ_fdw)7$o67nZGT7a0I=*O$ZtWYZ*dVK=fbIx0 zOn%8RgqON;f{n3cG=sxkX{XvL)$GVH-Tjfbo2nEjpBhZd**xpaCzs>LcdMw z)q{Ww;f$h@j#SM6M;26-zu1dtC5VVM4|`svQHIA!AB#P3DFOtBOF%1Qcg@ueQ0)}Q zQ#HuOdbE&^B2g|L@Z+6RnS9u`hB8N1^d&?tP-Eo(9RvtX?EVRFyN>Y9_r)p!5!XMD znJ197+^Hxdes_CZLo&w5?FpQ@X#Bc2H_?954P4Yr!Kmd(%)KWrvBi_O9hQ+b3A_&q zDQ?P($kzj~jaX}uDEAIj*1U+A7hw}U;u1&Wl_Z*gorgOxwsHaj_rm`jaGcisT|?Eo7Jil3odYP7HAsb|A5TW zKdw613o!RpR3)2hI2`bJUi7Q>N|2!aXtH4g_ zpeR?4hxxJMk?Zwa4`L=zq}le+ANgV8>9ex?z}wH3rSchN`E-qE8E6!X#dAO>^$OqB z(lrGZ#24-D5Mf+!Xans#>lQkXY4=^ zP?9H>U*~4GrffG8&uieYq1@yszKm_E>dlJAu^^Bh))YwNN0Ods=O>sk7g5AoO{C@^ zUaUZD^-u5r)?kaiWuiMUphD)xoOKY+SrK;|or4q1^taQodG6}xQrs-N^my0hN?A1J z)}3?aLsNnk+r_OM$4W7zHHZ%Hu>`lV%=v=`7}5coVKf2~MkHye52DS{;P#f3H+3lq z0gpI;92e>$f0hZffMv|Y7ac84hbTGn-M^aq34{aB48P0aTjJ2+J15i3o(6k+;fohYGUyKiU}Z9 zjSB#(D(rmpl!#SA8*o3t!$yWVdTblrMSf3i0+^E##WOZ+&m`~SY|-c{fLXOrLROG`@4-)WJ5E|j^Z&l<6-hJVV=eEqPs%a3J zAO*of+CUr@5-yQqT0p>42>sCs3f7ppdKE2lxPu zHRUBvU+0F$2axzHqZNd{Z2N^3PRhc}{e5mOQ3K}rBE(ea zC~cXXqqSD*^&{rDH;8lh7C6jKmCng@h~+{j{EMAIZx$zBr#td>zsum~tIBx=QLqHE z{g4F;q;keA5>#GdMJ!X~9!ft?`&;4`{e)(8YA1KRwbLG-rGZ8Uc4ThX{ejx)%9NC? zf0sQ5o;UEq^;7RF%F*Z}f+_fLe%>W2goBSaT@eH8=-1_L6J~&|V}TlSDRTc&mR%3m zwyX$W0_Zq?>CD(-EMh0{p6y9yTAZr6x%Q_h#}*4F&eY$cU@IP}QJTY3uwVdHI3&%D zrTZbmO00Jj1c2X%-`I{xZPi1fk~eVGG8GdtHQZ%%tCf(4f2 zHK~N{NGd;8w|FMigHkgM!H3hq*FZ)9C4Sn`i|tdh$rEw@NUap{PJS%){Jxjh*eedFBvr$Uk|ZSD zTJOg^4M((rMv=xJ5tjuA8y$;6w~)Xz9FEa|Lad!tTWuY-2srRGaIUJ|CplF?urRW@ zD|a7-DBQ6){(==%WN$yHIZTy-Ng<)9F4u_KO3iZU@$tV+z|c1b4g;!8XE!;$PD)p^ zYi!qEg${M1W{%~*W=k2#t;r@|ANEn2eR~v1`vt}N+g$?6BOU+_+D@<~FQH>K&fRZ) z|9fb9YbO^h!ZesFh!czq`nxnEI%`3wQw=+rB(grz;S5BRk^D#TqTs=Tb2WbBi;9>0 zI3Rzsc=`Tr)?XyE*|wn=aF_D?CQDLC&td%|#pRY-tU)WL;MC8);)LCY%F*MVxHq<1 zPT!9$?mYg_jNG!^2HvF2p=1-(Us&d$g)!1GINCIx@$<&vcoz!?bPR1Ex61mlCEH+h zfPwk%753-hilfT{&a>Srdw*nREMIlm|98=krhTA-eHzBR?LQ6;g4a`Cd*QT>82bF@ zcmx_0NB(bC#m|HMf2r`cOO#jP@bZz++aUMNsF35tssA~k|CYOEKq*6a=<7NKZeNr9 zLq6<=sx~qIE|rMFQw@zs?{e&B>&6Z86wrWktbG26(pr4SvGs~SIs(5{6wCC>dd$3i z^#cn9L9O(a44Tn9s0IZNa>7cXvf7rmF;gI@Qi5ec z!YSP99@B4h?Zj^t|1eP{&D%l9dtV-e)*w{2T%*joqkNt-V;nteJ+{0&5LD^SdXz+N z-dvdH<+yA$<+J6QXrUmGBcKTQZ&nep98NnNt!aPyKHZdcu*7$`UxvA`9u%8KY$JZ* zcuPXR7WDns{0Tzec2Vnk^p$SnMM5Y^7#GIx{N-=Q_jAIO=|;_v@vimGnX8Pttm5nf zN5CczQNrS#-#cI{wrTAX@I{p=sZL@6gOyKacm2xoxf1T3QSohbBuR>b{i3<5u<>rQpuTU$~su3cC}Hv?>fyd7Tdqd)O@JxaRH~Ugj~V7Q%k1 zO*{j02;_H3tdo19zP&>c@X5=g_D;aQKe?I4iZInY%;#Dz-TQ)bJS;v`?r2Bng{8TYB<@$MIQBSoZ~nzDC_ zCY5^G+uNgWNc3dh{+5jlH6{z}(JpUyz5Zw7OT>q=tF<|AtNOWE|B9M$ta*I<3AgZ} z1Lt2G-S#(ne=+E}Kv~XEz@O*M-jLS49@)OTf9;z)+G%gf0xsO?RpVOym(BX}mh)To z{dw<-&qh*@VDzYEzhcz8{j+l>+y>-KMkcv{oN4ezesrREm65^(=ig7>EiW^f<0~pi zUiWtUxJRi&8uTfBef>Xl14&Y~o&|9t-Uf&Mcb|2slIpAW#${C{6V z@qG#z;9)qt3Rp9mV`7Lodq^sxy2X8@M&qgCi76 zgc|p)Y8pw)J-?Nr3eoX?-Sr&WqC`u7LY}c78P*&)FCbs_hU1j_B$HQBH zzVvw+cm+lfh#PD(8+S4??-3;|i8WfR`eR{+Fi{@muyine!YNK(3V!s{ubl`&wG+~2 zMb`|?7e2F}#SK5?*ea09U^aZF*H$>6`{`1S&o9B4urzwa<6@T|CcRf$+_K>Ct$+YS zdOL8?-NJ$1cZ5rx48t`1`+0sE3=Sh|6MqJ!%3D)ylsJ>)8zxCet8u>mF0m6mmuiSW zRI3}vwyM_YcwQ|y{_pQX_W4CL03&CV^NvaQu>CfhHw~qu?^OlKl!U6)N)}2>c?trt z>}w|OG_s9?)S{=`)dGe8+`%#9D2C(4HR|WR@(Dn*KAnv*-o_#bxOwk(>3iVVTw=<# zniaQ;iz|-Rnf^?STjp209@CwW>czErF>a35(JW4u8!W!0915zL5{<8ap2`zJkSQ|i zHn>?)+I6&Zmz$3R&YaB>^xA^{6tc*{*^u2+aA@AQKUn?01x!aCzCC*5go(cUb?33E zG9F2sZGze=C`pVDRX94ZKswC7k`cZ9B9{esKNbQ?^>HeI5ddvG&CVT}yI2j(dnsCSF@Sb2)O*Ahp1=uFMR8ZRPo^;QG z4vgWZlV|unu15P95%Rt)&|kU{d;?(-J%WX>{E;~U!jGuY$k`C(xxPn>uJ}5yfn!Ny zPzWXpx*rT`HvbU4{%8&8`GWTE4lXX?GoeUmcV#7SI{BgUgz8lbi_>gEG}mIuO;vc; zZ@0cCkz?1f6Y0-bM?ZSBjoIm>6(3h}v%m7Gqz&Y_BE>2sFQsnweW4$T{^5j9tWO7x zRV09B-Xl90y$z`@HPL%O!mX5kC;Wv6>a&upm(mOBI$@ynW>N4hS5oHrrYh$j>;IJl zDBPg?s7E+n01c%eEv30XW(F^4&lJF$9mUipc7t#TKj#jIUewP!AfQT&co<}ndQR)>)4g`T z0y#~jT4I=Ot+z0&<$JFO&hZ|=cp{-uG=_i(UqV;sz`@b>Y*8fP)RYI3H?@?AZ)3zO z=>{sMNq@Tb6nhJcixV!AKR?DH852r*2zoqs(El^<$gxYJj&&D#I)l_?`Ha-S0t{OS zQrbo$qsy9spuSj7bes58QPGx2wpv=4OB!1*Uih}QJ()mP6EE6sHv56QcY$8#FfAvg z->;~XG*uXqHt~IeU^|~yV6DX46Q>qbdA|?&^E;7Jc94bFiv2Fn>Dc4VEXyXF zLf@mV96MuBh*t#(p%^ESJ3vh{>We&?O~tU9%Rx9tn=*g940SF8(X3bHGrt+J$?SQv zBqJm>tm59!Edt1 zE3wVlntC<_T@w?N2}J`?kcLsjj;O5}N%lq^2|$+#XA=jqzaf%uaKWJ7N4QA%#^a8r zeEez24qt~AQ~m6ih@=3hF51HX#D(U=&IWZNg7ll+TxeM5=H>#$GPpcfCx$1xCZ#2V z?4fD^DDh;w)TSA!fh1$3%f`XFI6-vXTWf&y!MkOT!hrJ?cDEyiIxgK>5Y^o+Dr7#@ zSAoREEQ_9;2l*i+^WJ1gX~$m&`8iOF&d?f!;i8(up?aWXH9UZ05t9AV)zxkCOs$;s z#=v*(KtD-4**}8hZ9$gHCDy6GG8{CnteRxZnmzMIO~>jLZoU2P5P{(LGeE)5D}rFCk0$Za4sfwU5ZqgL3aUkjrCsy{En*>e1yGP`@@9Zk9va`R zNRkZ1v~WbvWgg*#)4yTRwXeypEa(_;p19g}2A?BdJZnBpC5uZy^f*p~Vc5sDHjOuQ ztc&PoO~h;BMfFq9+8NxnfH-L@kaEHye?L{-% zsqeANs>Cq&r8E?u!%Z!OMi;^MNmC_Ht8~JkCygO-W2g=8r6B<;=zydBz7-zWXm!!X zHn2D^i4#HM#ml<;si~<=?HhL;{UhBJtfq@ATMT5z;8ORAA|}Yt{RzHH2z1F@3Nb|2 zeYFO2k$Os|9<|>!X|-8MB#igUXoM;5pu`gRi%7#nc-rMYjPmXvao8SHWwV~A$F;*Q zP!_bMXlMjsC+o0^SzQw?GA-Z|V+{R3x~)0S!GFW9hlbyYnYe$0Poq2}b@whE$Jtt* zJZjCyg#^2JPxhY&;kdvp7R{U9BSqZH4t8H_D{qY>(f!m+s5>m;_pnLHw|>e*ULr>l zmXxuO0**D7EAkhinZWeyVVU;gs8ki`A7M7wo^975g*0rNW;vB-PiA6t`d#!kMzhJj ziv}@HaX$0Iy3$`kcq^x3Fq!wB*ntCY=#k?2eGhd{$ipJ*_M+Fwg~`Gi;r%*tUhh;L z7#Ydim3nAcU20)4dB64vDB?;%#n&7}+$9{k3vp(I1_g|;)e+yl|V=Gc(gwc~6o&%pBc;5!Pn^0qV zz=4#ygA`fP7)-NF_985(CK)a8F)E`Q1(`$j!ZWRqE#XUKD{poPzBVt~GziPhQMhJ6tx58Gn_lJ%xn;_JXTUNkgp z{SrBz0v#x%t;%ESR3ZgdmGFQCgxB;yZ%a?&2km$t-DvpSbJA+%%?0^y6(_& zLg(*((9b;pD{ulsV7gS-?T=m)-riH^n`fafua}f(D*fSz6}S6Xk>$~w6mGi_7C6QbKqY*cq3p4a1s*m-=SMH z`C&Z;nhjv$KO_nI%w91!*@e>teFBVj2kOBGx}S`|+jy+>dOpkGbk4{&jL7I`C)s{jWPkzD3-<_cFo_L9<+5<)wwL8&m zl@vn4kx65qzFD5=xx6o(u@nZfO;PZ4(8DENt$OM+bp- znDKN#E+_m?#{1s(JNvz^_uT{+Yyc|KmOI*D@uYV9)%nzeA%+N zeo%P}zf)L+vj<0WYl_ZKsGpe*# z)l2ty9RovdPR3)B#`6#|wYG=8V>%rO6+^tSZ(!&4?Z-XeNykjm!7J9MN{(v5H`Ei% zO5EYjSol38{7Hh&WzW`*J%RY4ui_CFghoTg-(iwuGxi+%N9fp<+yjKPjuE9b#M}AHQ8l&+oE{rG}hcm3CuPwjtwRJ0mVPw~nhBTUf zMI7a|K?`CgY+@Q0`^0Ek&><#n(Y8Zo?*yF_^Dev4%dPP3O_%tA*fxR|G{2m`6`Lzt z_bfEL>PacwA>Dql{OM6UT=|I1mP3ruz>R$DEw5islvSb#IngYxaZ|hJu32ZK2abT+ zj<@Vt-%pu(XA9ps64)nKuG@Xwotn!khKO4sSpYG z$uDTpVaTBO%MIa)-J?+B3m0yjSe+TpJPS6_1N8CRNw@_XNv8t>RolryteGzR#of`;?qZ$!o4*@0^dfx>TTqa)6O+9>s7`(%P?^@6DPpv1XC&CgwQCc@^I0X4f2l&EWN3fxMF^dfDx>GEC7 zHaP9da(yO-3eRp?x*8^Y@8|Qp-`FTiVclKR-EMV-;yZN3c$$rz%3E>~dWl*Uk&GaawIYaZ%0!N7EFW1f-Y ze<*5}8E`eRw;hG`OJ4<-_A1ez)s;3S>FBUh{L862E;Y!sN?P+vghT}pEtod0tbQxcYXFuiBjTel~Ut<4rl5!b|fi7 zEZ0PwCNqhn4zKhBo&`r zyhPsR6P@&UdFSpI-GCFdDs*0DgON+^-;`TQUDEsOZ_)b^pm#!BzkRJ9R zDlN%md(%!2S`|i3C7vvcRiqIp_Ug`S;}ou&FW>9N`&&FFf9{cUs|3FFok-d+qNZ?h4nZW5 zsl%lJ+Oetb$!59`(vpVzt$kLsOhB%5xQ3BQeI3Lq?z&+vl`V zKtX0l4uJLKV|$8#SZSi}dQd1HY2Kc7J!!1Bw->dT9EmoDR#xlIa_`*P!q;s|bhS%k zxj}f^GvAWCl@;qXRe`hdTACZQS-kYd$kW?K&!ZbUJ4h@q0{{RKc@gb7c2VwW0-mGk za$%X!S0<}v*R{Ev$MzvUes`rt>Kb15vtHi?n_~`BN7_7|ccgJy=;Ph06$Epl9hs^cM_o+{eNGk8Ye&f<65Uz(e^k1T+BW11;AZOs z0^DaG#KC0NDYE~vCfY=Z3rtGRACHuusMo!a57zN;H2cH`F`f$(PI3bR+dkd}1!AX8 zMf#Fq-mC%VLi+e0e!P|6A!UuKjZ?+Gh*uvHq4N?=oINF;eb-*?{CPy0Bzf@5 zIR^loe@X-!~-^LfMzp zi4f`T)-UPMQ^p2^It=VH^+$lX2B*;j%`bI2P*()#ypr59ISLHTR*V~JwXi=9z=Y~EC2U>8c{X>E9Tb$fp;|Wl~vHmpo z>)VTb(0#IAV-A{;XxWindhjOsM^U4V7P-|&%rt8+t{6!kNI-V}RI91bov&ls`RCHe zQ`kNs%w+}b`zvR$91R)PGonyguTqNLTo*dZ7;BI-Wj<D-}(0{FM>!LIf`S8S%Yp1W zliIGku|zXgK^AQU74Y%ZKu1Gcg?JFvmhhPBYl;TU-_oHcYwj@E57AvyyGD$hWVWkb znBg}u=TXhJmH z7q|NIUIY2i6%CWdP zbtK!)#g%@w;$aq<_ga32QTDwBdkhu~Yu=Rwy?T1nLMbJ}d(j4R(SAcF9AKbaV?R3d zxUqKSko|cDf`1blB_in#)&22|;ZwCD-Y@8@O76nS32___2-->!d7{ubG^mu`M-HSl zs>MNM0PR)8lg2XFNELPL9rkdkX>VdE^E;}<$GO>(I#p{}^PeQ0%DW_wIki0~;g z0oI@YRe{=wKr>JqTd#Zqh7dJz0goljB?lpW03uhqDHveP0v(&jGn!+ZYGnf*Po8|Q z^qVms$dKBna1EKjApvSuwezsy{eo_o2NYjWH&_}dTUx9IJAPolR;&qjEJ6L*R&)a4 zN3ur1vK%!YP?7$UOg;{}_9{FIKpH@+4IAf)t_V8xgAr-LkIcJFbmxgP-%NP<&Q+Oh zU9|v4904))ScQC5=41hWY$Sz%Pge!j{%oNp@Kk{)tV8zdsWI@M5Z#=0d4TVx<%^AT`r3ys<6`cO=~2NS<~ zrk50VuRdPP?yb($J#K^7IYigF-c^^lA0kW6Be8H>wZ^V7!6ns1-nUH~#VbWj($`PHQyA~qC z2k2M=wxpfzWEP-{z#9V)pGI2PJE5SV5WMv=*@O-YPyvUz;~8IX9EGWLt`-a zQ;NmH?TKSKJirj78m0@O^9K|sLIX_HqAea`OvF-qC+CmMC6ht79-5r9Y=MD3G7jXY z``0_%Tf0Iajz+!QBml+^kqJFY1ZY#Gj~YnOpp8lyutrY`x?w}gz=2mh;9f^)CCN&B|_z%umLu&B7N1!7i zR*`qZnA9sU>1bQW4@N{xgpy6oxJ!KLVKf|#k2VsSjf}m_K6c-1!#T469j#$gtHi-0 zuZ^(-MVUI3@Dww=L?Z48!g!{No_`dKD=`-wcsFDeGv*6;Sve8mWp_h-0~YLO+zN~r zW_6%TALO&j`PCrAi2kObfoa}95ZvJYc;JPsaz1(xi56=DPQ8u_@zO55ZM7; zG*Yy5Ptmh?ESJ4i)YN20m2c)zpBp5*WtUQ-&&8z{^n^TTh#`3PXBW+pG>}_KAIR?g zRBO+j82eCA#BI(Tg0(w4>Z3e| zswy`U(fB+TmbN>VB4|#kjR~A1`7Y)0%w-c=I+_b@Nwq2l{pHcQH$zN)EHnzIDDE`<~icL#`HT8ZB zztdp#`F}2ng4GtqM_!2kXbS-UVwUY`b2GbD?P_OC9_KNT!Mmru2!f?6fQfWuQHCgQ_f%N}2Ev>6oyXpTHX zK3yP;ojwO)ENua%O` znuXgYVZ{JgKJuf&p}W7J&(oFMh)o_V1nlt&;4mZ^E)y;Nw_<$1_L`MJ7^4<)W`p2P zv}9CN3e#zu5l-Psmu`~7Rp+UkWq#bOd=CXI0Rbw8nGnPbkq5KZ)On8_?Ak^Fd-xL8 zhTQ5@2ptn{*Q3&!=AG%%>I*S^B28^vZe>9!+8r2n&y1cPpkFdIR^Bgx(2?YbEZ)lFBwx2zW01ct@gJsyic)cUAYDAm1IE6&J3hU1@e! z&z~8%)`Q1bZ-g+ps1{$?{BfuV$2j|2FPr*t^EMaEu`A}m`Chsv67v)MzcFVb#fpa( zcvtlpkW)h-Ewju`#FX2cHErLJjW)lJw`LIf$6s*HVp5d7UFx`ui zp%&j0p%ugc#iJ;xG|J!kbRDw6T!|ieZxb=B9U?}anng2z7J$vGU$tiMyDZf9V|s`RdDcI3P4i_VCkIaT?~(?QxSU_hTi z7S8jsH$XTI)OsG@sl22cV>a6a27b0yV_W3*;}Gah9WX7~yK`}>g4b-T-k)!Lot5*| zOpe>0O*6Co4QA!td+J;DZ%5-CC#uL#-&r~tPSVZ(D33pbF~Vx17~i{nEXmP=Ng22a zQM*XF)?tr?77wuZuRcV76z^o6q+U5yr%~+z5g~cL6E=*t;2LF{FVAN1r+_^}$1NC% zf0Yj0;Q1eWVw$~1aq5d+K5qp%8L~f)T25A?G{M1>gN9Klz1rBzF^-y zvct8h$m6Hsn`w7yW30VxTR%H#cXU)$ZncRBg~gV1-xVTWqt(A$N;4|k5x<^lB6v$3 z+Mxy_#Q&Xp%Pv{Y@Yu*<^ba^j%Dt5RrO}*&ue+l{d6W(#(g<0BB7YpfTb_&Vrw<=7 z>K?*|3CN4#pZ%GxdO=p`<+ZkZd9b`3DY*_o;O34NEvL;L|9j<(_{d+tRjG)Ok)6wI z)P@1rwyLR2>+tm)xdPWYb8}>r(^l03`Fg)y$zk#mxLL6KHVSTP!!5jZ7+P;~Kr7$Q zG@s%T>IFp(Ow=*f%sD&6SW;zEKm%nOt28JfP zwh9c91k;ME3rfP?1 z#F0S0a(`Wm{kvy&1Sdn=cSR>bBaCyP)}e`pOmt&nmjL4HMPt2%5IH#9Xs_i} z(E=WB-C;ygW&OEg=d;#M*dNpg4wGlJ;s%)jGO`yLaXm165bK=(=c@3MfEYgc=mn6S zGj*<3+%Lp%eU6`BH5w3*7@=f9$qy3rJtVZ${Q#O4(p66{D=sR?B-23<87B>x8wFycJW(GIp%pKMiULGn-&${N3 z>GQn!MK)gaZ5&?AOzxmWu}^Q>jH#AWoZxd=gdX64VfmC1;zHE>9NLkhG`$l+H+n|> zwL8GZ!?~PTnu{sg4iIr^Q@|72G;zy7pvHarJN@>cIav=yYx5wx&s-R)E%mhf8)XdN zda{ztduufwD1H>Xe~HWBm)EIF5>QPOmnkTTk({h9%{yvhk^~OT9oB9@Kpt+iKaiB{ z^OQK1ucz!)go1u?(V&2UC9CN)$8lu)cmz8na^K@JJ4A?UN-n;p(w7jzGEt_rKxrOO z4)M1uo8^@R*>em zNpGyRt&n;V1EG}!;0E0x9Kc)@QtuZW98)gYwhUNj5gL+ln7QogM1AV1ZUTl+HLT$H z6Nt{L3*B5tQEIJw^cm7L$1(J@{O;I{1> zMg-&6i&qRmmjMnAR-Ldi+rr#yfyOqOSV+}RLwu|5JC9mFy7}}QHR?tgY3ITYha%Zp zw)GY39xzyCipmAY8r9IoIk6fNEQ488vz`!TyY}61NO=+R^ERC%2}JRcc6&QtwXy=i z$J`3%*hn{CQ64cvR|4hJzFQLoZj~g z3>Z(EhSRShvx&l?Z)i@J z=5TO+z^$A|$+6swg^_tS(@ypECA5OeRJ{|*nvp$1D~8C7$s8{FYexJzGVx#rV_LXf zP(I;}FOVC{X##Zcc7m~B31;!U!qm_QcgR>}EI#K5xzY6COt~H#ciL;_<(1o2@CzKQ z=hnCK8sOFJ8~h(+0uKX>_N!PO#qF1jKV{<1y3J>GQ2r^bv$Ew!2wdoS!7MGY_g?*- zivT7My^*&alFe4iW$*H~amFv!hHNv3j`VJa?zgjpi))M=z>+6n4JLl=jNY$VpeQ6% zj}aB>^=zC7BniY~COE58+|~;?&ApKU2NI&>2IL`YtYGBWk}($BHpRV_S^zN>Yqx&4 zc7bHEH?>LStOuN`INpp9hl)Lqq#mbGI6rM8TrZtm{}N%uGd@dIyeCHyLjY#|B0vcf z-!VN_QIsSM%w=8giTdIP>=<@iU5*_?F1aMGGuY*{3(@>v(+Pt9M=goVbV7BBl@oS1qu_8KC6 z-b(Q?a9T2Fj+oEibaTH28?;WkxJqgFjT;!u8&ScOg1h3WCj9J~ul`(I2vS^CEJuBQ!71@haEy zWD5)o6iop$%!XZZoq{bo4(4Rizpqk%6<=g(+BQGXs_Ryd;~ecja7mPESuBrO0?7Na9SstV2v z0x#HOkGlor&DS+T2oaI~#T9L8U=gZg6h+5lBot|_5P{LAL~V|0177^>#q!An6g;ddg#=IHi)(N!W-eRV90`y*BtIX0x8!1 z&-+CguIZPirU^FCexX^MBzsEi9?6$>)_LfDSJQGmrB`x@fXns6J_|{@244dLIN5c= zjvr@J7#*@HZoEDzeRaDg3OZmjNC5qjwHVgp)sjZ8&ZyZpyQk$${O2=6184 zLkGjhk9wi#l>W>+C#QKp$l4fJv?nJt3&qe5nwdTFD6y=UB#@lF&0&VR%oKouU(~n3 zB&bIT{N^`L?)a@>ucB5>Pa?0>(o#)nUR8vq+!yMFC`7MLy$Xc5rRP)?Vp~{6nQ~A> z=bLTcB_~na#7YkcV9);__TDq9>2wPpWyUi)V}Zd2f`DbLC@2UhRY!zT1XMtfsuV#G z5Fzv=sH1>1N2E$o5s)s@TY^fF4pKr3Q6e>vP!k|P?tY_qob|sS?w5N%+;v#XDVY4q z+xFhie)fJQ-d7ZQvaZ7j@$f36`T#wkUXo1REP|d*T~rs{W=3>Tjlp)5A0K(SN!n!Z ztzQ*598B{3a_)i2`mBI{Yd%FmN5Xt^8-N>vwr3_8lq-ZL`wP8?8<3cmd2GV3{()F55;aG z;zlEm(xb(H3A?juFQjW4#XKFLG4bcQ+wrX%LzYne#Fxj`QYO3L5+?(6w*8qgy5L`+ z))icZeO8nL*-4FFrGSx(~QX6BZHr^Qs zYsc}KJq7Knoq1#!LM`oW-ucB)J&6nrbEfG^uPabLhU3e73RucPZ< z(9=eDenKASrDwURsdp#~?{YSb;MFJ8I;3+dGXRNY=o%sxeDjxYD^u?IKJz%aM8u_!C^ zijm{hBJg1=n(9)LT^##sVK}06^`8|Er2rdUCTok_r~#vt?O<*^Tc$3Luqw2~gIa&P zz)f?1f)ZJFgCuTV)!Z9ZR^WRTc0(>w%0y`5Km-8EI*vpcDASw|ajc|uSaSV1>p#j?qOOD zob@dxD4n`XmE4Ibw3^B_Bo)Jp7eZj+Z@6ZIVouAfOfqD7H_DKY`32+X=2^ZH*LRP|aOm0;1 z`Yf9TZ-c-*e=o@w%&W`rB9YNX;FqA+o>B2KNXQ&Wx6NXsnO1U4EdIF;u`4ign&*Pn zt)2}5X1yG{yY$vF17ht^ z89 zdgPfK11D5s8w5eD0(LbQL7?UNJTcy|Dq3Jc#Np!2tEM-iUV1f{a z%fi^R=7A(*Q6t@y)G_#39Vg1qU*;7n)TP%^l0B3$+Ad>fF}H`}7neL(4f{E>s>vQ^ zZ+3Og{LBqXF&3J_FW*zSUc8y-xJ^v{I0%CY$QDh~}DMdq@Euntx@0=!~$X~@(z+p$VQczz9pM7=)IaI3!*881W)f5wpXbKf7WWi$hHpmKDPQx=LG1o3(?eI?e4#S;;j)dm2mRL|K} zRYff=vDBRJYp+}DulpkV(mA;l;erUff?v14 zlp&hCT$=$#62j^BgEI+#lj3w3S!clQb$2Tok5sD^5j#jpwntpy#buNy`kJTFeI`9S z@*DZ_e0c(guRL*&MX&%ONyQGO92=av=+yTqXXDU0R`jK5KvSTaa62Mwj4Ee(<1iv` zaxBm~oqPQ}$a5UIK`-_M^t0U>UOAP*WLvm=-jg!<#KZ0;P?n$=rp~2*#Zaize1o}ofV-Sk*QAKP^rR+ zpje`1FkEloj7NXIq7OyBZ)h!Y~^5%HO=EFw(lp?1;`^(w^N!GFl%2q<( zgcEX0m!q>0MC{ke{zV6}(#{#2aWjA=7HNgTI#je&&HilHMaK~fe{A>H{B@CzG+G^N zs;reE-={5QqN;HONMjuf&)3?@>ym75v`n?h4a(&Qyi4vf<19FAj$yMJ1x`09?8D8x zhceHtCpTF09^W@Pu=Y%o;EgmYO~ zq%bp6%FGw|j65@eWUV*KG93o`* zcQJ7-AAwuqFoD&DVB@G;Ujo+`#{$kSucegeg~Dz0ZSZn7iA1L1qb_0d$goO=Sp%(? zfNU!fQZy}=Ho504V5$L=6rrA*M7d9&1{vbdWT)j6wQc_KvgmI-HoRdTSqp?$f*DH#v2oD*ec!Ei;o!=Lh|RY`%iW=SK}o)@n()~0?G))K#)JASu0T7f&53n zj4_SGYm4npEw6WC9z5(RuoFP&Dfe7XUkDc9M`(OYfi~m3yC znevSuTn+AQ^=}^4>a5U`3WyyNAU3j{2nd5{Lj=92xSBvxu@r~oD^^u3$LuJEZ0@j! zEp7&E{+B^f5i>XuGdxlB_~8(-2B84xC%STv*@A6v;bQoH@~r83qostx42GMO$wr>s zpCNYMj@D5DKg8sfQK zKXJWXNPRtVhfW?~KU z^E6_u#Hw=Tl@)i~^YYMfcJ{g@jyM{yM^0CwKyT;k&+!@Kp6S%%#OWfZ&;9>gEFBMu z%pP1ue}J`r9Cz|d?umn~dwwp*$`BKa_Wi^^u$7JuDO<@t2M6*uI}J3No( z=g{2JJ9IZiCG(3(l49sebDRHLn8i$sc^~r7&dYQQiCKCuAG?SfTjfyDF} z47A(APYU{QGEI)RW`?ZO-Y#U%QSUi#2Jtly&%^Pk^Ac;SLy5ctN9Ik zS3bbA{XbLATEUL-u2^ID{^qae1GjA1NA163!sD~P#@@@N_v27f;f#iWdgEvHZ^g|% zdso`>z!iN~4M)BY33iN?-}?an`7YOs`YPzwxo;&?0w24@gEolqRApAxNHz0e`g?D0 zE&_G@*qr^o?+-S%@0V&mKVi?HfVUp^{=@B7<#YNm#hZJApihg z8`J-9*$DjN_~zdZq7aKGe)SJ`=YH~kO-s1L@ckeEYwPlTD7gRezdken9*_TZ;`#S@ z{6Fe56P^xChSx*{$<>uROp}3a`JbXu~KmCvIMAJy;9u zvd7kO_8+lFizgnV$gVX}>;j4GqET9W-Ck8S5NHD^Y6S1>hr96@`k-3+azZLVG+QN> z^^Mpsg7dpEE*G0m!^I zESIu^(uEMz>97wn0v`O?;2Vvyzo#6zxAl{kP zVZc{pL{JHOQ?9lp2V#>-yhmQ1mprRxRp7Yq)rz;n9pPjCAYWo0Hr^Q}h|sGb934Dw zl!<65Gm2Ps$xi28H7sY*iOi-ohJckI(0jk2r&H*ky+L}jks5Ix`7KSr1)=-&ed7Rq zo4>;*m!X2|FA{XGl|33a{izfYC1q2kZ$fCqO?t;W!w9)U1_qiE{@FJq+}_|0zu0>} zL_j6mQYFinnkfym5O|$~8C({sTfvj6%n%jC2Mc75GhN3HOHxGE;g$m;;o=~L0Q^M} zc}qF>bP)yC>ehlX&Z#fkF_xjWXuLY19vrl)6ef7 z!P)g&Dm{CAiSI@3Oqrjb?r_P0JVsOAN+iL3vFLa-TQ~{r8aE*hl zLS+I_i@;?Xbp_`@xq`8*^@9?ehT4i{i1hbXeJkZd$f~<}O_l{@04{}W0piW>;nLe( zj}1d~mfZD}03bcM!+6P{eN{!6{5O2)*&S=ygM!9z`Y6ufMg6Vn%>01pwxN;nmARRk z?GOg!B`(>KWdLLxQP>|H3(>mRvSmzfkkinUvw4l0`mi-7sqwvL*1wl!vQdfw9YjLY zLr~~e?5LlC+hgi!r8#Q4zw}FgB&YL*C+)yD4um-c@LL0GY?#PP_06UiY1A;*mDA{k zXNcZ!-kwA_%~7bFyKYij4LoPA>BqP~L%bodqC~%=x*r%fyYF_K{5) zF)Lg}e0{Na%P&j-oormx2VfE`;2WB$_CN;9hD-qBYWmB(Y8bv4QH81q6oEE?5)OI9 z%-@{{47C8f1TeScEf3;`IsuNZ^R^#Q7IUC{QR9P;L%$pr*E*4Y^O1rm+6L-dv}^V7Im|C9fbJzT%it78 zhqKU~QIzLHIeXHXrhW7fz`zisQ&Hd;8*PwQI~xP355>g~O?K&2pmJ0|GFcelQNx5r zgrO|}xd1&;8?6^mc0o^im(z8kG8BgZ9~oVed^k~t9#r)T$I-=Sq?(yIXIW_!z=@|= zHdAB(Y{nEo*?|eRGrfM96k|3DAxHUJZqf(9?h`V7rA2>e$&XY(p!w*+<#AR8Law0c z7*DP{v`{>JhucE&mJJvONiZU|`A;ta2-HvwB0dq%#g(^o% z_}bqcfr+#`c=x9Pyj4;NJ1WCv%Xu7D; zAxYCadoLx$LXQ#S-SD?RsxI~4;e|LMP=EfNIqeHcbHp z)ed6Z@%|i%4_|php1`T-Izh+A?-~&j)^v@ZK#Nsh|RHYX32t0rUvl5+DKz z{nQIEM}Ob8np;EQiZskTK33!A5dAyQ?a&dIa+L;Lne4f2-xoklP4R%?ZY#n$52=I; zLgnaT5ZyQ-1^DqUPI}TgS^BIg0zzaz9Gqw`ULL*Wuz; z&@vvI$sdOu(6n54pdAJ8!yhGfT(yOqNDla10=w5{`5}5KDuJAcgwmKl-u*3L=7P(Z zhKJGo`&K--ib)X(SCC(WDh@gezz*5Q8s#b6f@j9K&M*Brq~$*eF47eXEm&93w8YPk z%vqx3iHZXXiHI2qkQ9*5+6nN~>eCJAo(a{#0WHwN|6n}UEKx6y3=&8cn0mYQ zWBjLe$GUVF*=UYtAVuFErSO7z(tEbqt^ESl;;TX-#?v}&p%U~Dyr65Dw>8YS= zUErusqvfJY<`IDZm#3@(_nfP;!t<3&0?}eMY3k|)aGKGVGD}9nk-GqCB9oI*f#Pt4 zE-0h}8K{nnqijFfUUI#R^LEYT;8T39%N3pjh|BUg86Vr}W*f4IFt6r*)bDicTfb9Z zXo&Y0D(2p*!n}ZXR_yZuzcO-o6HwNmT0YHlRY?22+*wS*IApNeT(Kg4M$v#c#tBA* z6Z9>#W+w)GvS!kz-4Q#eR95daF)Y=YmuJ zk$bCU7d4agL&_9^2a)A=Ser)9X&j1|nGjDI2NRAMd~9zK_GxfI5N+!`f$Dql$V`7E zu)kGz2uo$Wk@iM8MkuX~l7Qr;&#JLE7tOQ-_u*I_P+$B3#trHV`Q@f_8oAN2H>H57 z!m#Zm`T231`)N=lg`7={C_UkVVs1B=i2LatqZH!ooBU@^h>kgn1Lq2|>fZ60yJ7fH z(KRk__;aCK)eI^xjdezJ5{aUlH)B6jL=-|<8toDv@=P@J^6r7CN?#h#>boJ8QwP!z~6K=3IX5b%V;mi`y;`yatzA zIAhnG;DpXwJ-M?LEjeyd!PeCV2I^DmXFId9{YP=r4Jwsxdo`IqVRj@y%esDr(7n}; z`ox${mH7?^Y9;A&`T^@+`P&a)+waZ*YUdjR3`x|r0g=6s>56@x4s(TZrDu$(3jedd z^RaH3*M#r}7rsoPpH*g`bdxZU4BJRO(3(^YX;jDX;C2+BAf_@(i#?cx$}m+5@)5L4 z^Ew)PAZtw?c5dJDwzyx@U+y2d_Q(ZqCpKWxM#M5-+!@?PP8GV`g@0-?|Ma z1QiZAb{lWxtB)0XFS$(wT#d-Jciz3Vfzt~u6ktQ@HZnS7kj3VC^wYjr7xN$oLyfp) z%py%@lR2CD=2*zuM+DT&(b#*$D*MaG7lGTjqNW=Vmx`jU3!ff+UfOZ%^(43bGp{BB zfv@6ip|0;o4k}9bPR;?HS`Dgo{XbovePjzYLYkvb7Jz|@YQLihGpk;BvOXd26sVqh zv|fnrHtw7VE%DCo@ObaLA!vYjC2H8A6jl1dAC&#;by+IJ@^ zldGZE2#p$L9T&t>ncI=aC`dC4#w|4kBmNLHFWvet&TCQm&Lr?`WgR8_LE=?e)WhXk zKjXzYGzW{-Wztfp#rICs;yd)~F{Zudn$;(ro!@p0^a03Ep+64mTcjqb&gFRukPvTY zG;FMEIQ;sySb;sFY)s3v%)8n=6EBN_K~eJhv?Vu~56!>B#Gs6`(5CYd zinX#Ocl9UHs%7Tq^5!8#@^^A3(=?a1+0paTwNdEMJ5BV58Ze#R>AY*6B4-K~mH|`~ z2t8i_|Ey!U7I5$KWNlO?xg4P0|5V>}6C#{dSfG^{Oq_UNGCVCjI7C^WtAO7F8JC? zGOn7B%H}LPwg7XeEe5zTXEAwbXizbmqNxcw?;wKcAI+xt30%$$MtsX%`wh1lilPH5 zHstp;&uMAdB)lngSrxxMU;*V085O z_UZFUCeyC6x54e11*kK@w%_)L0F$FNqG{)Kw46{NpJBEOdD_W3zPw%8!!5p4v#hA! zf%$tosSfpWty9A)5wNYZ%8|T0uf=}s?OlBhw&16e!kmn3f2Y|Cw%DD9wzXjT1{1}01B}R_CacT7iO-xSa`h#+^$4D?7=sboFCRCPp188R0CE~Xq!9$(VWz-T@TD6$MQ=hQEoV=-PD7)JrFg} zU55HGX_M726JZdL@$FTx@I(b@2nM-Z3pS?h5zwFb7RQp0I?~6!au9i;{cP8(tZHh2 zsJ{`xIg~GH70-4ah$z;q`{t=qMUa`!>;<^g z0eXv}pCs$V;pN1k6es4DRrf7;c%;BIeL4xDGF*%o3$1bk3V?jAxmKLf8MR)_4N9_t zPUzix0`P+{6jmKGa_c_bg0MC;N-%y-B)%j%DNhL2QufXHbtyYsYI^XQm_EBKh7nJG z{_kFT-;S3+w?k#%^aX$!ORmzE#7Zo~`3d!Lf0dq4xUWy_fGEN~Tn3mKh=a#hn>h%? zC~j#9oX=>Vad8cZwk9hcTicEkPzh@DMl9XAx@t(-?PiS1OfSfIx9~CQUC!qI4(m0@ z&kx`-MCIVwv4JZfz{ArpfC|c9XCZzuSPvm3PR3))!dYM<$qb@ouele$-< zh5!guzQxZ^&DvI?eQMwvP0e!%dD4`^v5%`m^1w5D62WkoDN0+T4Gc*Gi^fWU*myzC z+&&g#dSN^bQFqWHn?yAar}SFFj@=hcXJc;J@TGkdV|$hr#=0li@N+B4xS7hZn|l{~aDk(+cPEBmKTz+<_9 z1r^P%AtO#^5IBQEXc1STRyio1sxwRyM>Yoc`B4P{wg|Gdh=(~gR025kMkobdIR5t?QMMEq$UC1)oi{eS z`Ua>Yf=bto%_-0;CIi|CW*D~r`W68!;FBfReE!^5rj_9o0~Ui8Vf!z-@Ss9~#e!&!@*zfMSBn6sQr%GuePTL1VrqGA zo@Q0LEss+yz|*#+Kq#$m?K{kE8RQP+xPB-?rC~K$1;FXDy@!#FfLOe37^>_Ipi&{P zF%NY$pk_~!0yP7N0G~$4xfws&VBYjO&etU1;8^|wuR)qW@ zL<6icla?)Zqiy8p&oC+o4kF^EMNQ`F5P7K$Vg6izu)5b`RIv|1P_#o{ zPy&NsV{0^^Iv4coaX1tG{GbLGRR*6^GF4JsYAj)Ncxa*`2LH)wu?W(Mf(48Acq0#c zlo5dqf)DtDm$j7SD&Qj7}t+F#op!J+}9 zgj;}w2=x&0MN|QQHU#yOAMeQv;PwfS@Fb{V!QhxWs`sOPB?nmmO;mjZepaoa0Eo5@}kg=sjE+*zztTp5c0mEwlD#FCij7M66z^H zTk`mEoBD*S8Emu3hO_=VMW%g=+gDO!S>I3)Sh)3Kvd8$N(>0M7fs6^Nvqm@LmNNKH zI92rd3=Y53uvO~NB!vV(ow-K)mDzc)&IuSA(f7{~mF0NwORxF!ShrsohI0aWaD_|3 zVtbkTsp}hEAqV1U?0O?m5O)glL>nPsPdi$0n*x&<%>Q1}&+pZ&Cln&?iiJXE)U~Xe z12yBSY}fIPv$CkBu?p=0YNpuv3>g<=l3f?j%LxjT#fa3I5kf&sI`bOo6d6D!FVkf7 zWKhCl5sxdo$U@8HVXGJG{C$#myQ>+ZSRpXN7WHfDH+?mZp#iTZbX5@)kDa}Lwc1!Y zwegvg9wPZ7TtFyKRdw_Fvg5m$eWU`4r?a`qLA-4E*27t|GRS@kv97~eVVgcq$h6XU zOeUCs*+ei>1rf`tf^HbZ3SeeePEpd+onn7HPMROXp?UZ+sv?K_C1g{PC0?l>>nO|4 zuIfcR$|hXCeN0D*^}Sg4G-2pIa@_8dzB!daIWK~TG6IxlL)Gswy#oO#h2dFpYzS6E zfSA{QRN)Ll3kp!xeG_UVq!Zvc*=w7+{ua=vwd!Lz2DTi;A>Z-_4hWIcP-lWluFLX< zfLp}RkGhUQB!VWz_++MmL9=!5)78l*Z4fiv?=s@k-jjv?v^rKl}4+ zN1F^Z9>Y!=olPApE}djKR%v9VpY?Y7asTk-{^zR!7#8AB;eFe10Q_a$PAM|g8cZ(` zwri10aS^~+`#FW(H?YMH8agxS~3)=KghwQ zNH%}%d+}{)t@+w-{4fa+{*-4^u<#vReq}*4@K+$p5hY1NwD;d1AEdnVC?7`gQV&!q zmbEn5!CEr!&8riT#{OG2?d2(hZ9do0^r$s0LAguHI7$t`K@lavWhkv&lzYkMLt7UY z1%jgWv#qu3p@7rY8`>X9nQ#e6D<9;!0qyg4*dsbL*S$vsaC0z#o7H0d&@$6P@*rpQl2UNw5MzRCCyh{BJ`Fk;;(C<@;O zvF}SJqEezBKIhH;m0IRupM=-LUfO1`JgH3&0q#1AHjt5mj8(L=*X$TSioQ+Yf>1DB z>oT(P7~Fr4?FO8_zFMlm;x{T&QURKfcZ->R(?wCJkSU)`SxP zk?cMoG$Zj}AGpPXBrg8-D=1tBfxCbI>VMpae~-uiBksfh--V-mIPqn8zML0?=`);= z=gz@?yvMqeWQ2slvB|YLCRF|@@zgJBdv?kn6Sa7ub!=CluhZk5UAz9+StA(y+5A|K z#>V_(EpK-`(5PU(v!PnwCnALX^sI8ewp)B3Te{O zOOuFSxSXLEJ6!UEmg(k|GW+jSwEy7`R6_1=FKp&=jlX{a$_)1ZAAC4ay!qE*%;SS} zcYgQ|{3p`X`S))g{vRS<|9hbS??$Nh+=^PuYwIf)Iqtr+^Rc&2CzC`jI$uxG{_Wd? zbe>t6TAwG0MBMQ>{O3f6F4BR0d@KD`d3>;)>OWko$a3tA*B8RK@B1i)tgNU_N{RE%@S5YrF)^(&y@C;R>4+8*(JN1x&KT6*|~&9)3+JtG!Y^qPE3&aa%;`-ksd z9162E`gVb<3=o<6`rn&f8n48Q*W`cuoASQX-@o&!RGT5#OiZoo+3@Ys#oj9yko(S} z?sj?mYTC6<_WK3C8ws1%6V_OVf}1N_Wi0mltICG4MnqOE=wm#Wep*U<0XMo7vEodO z?OoY{Crcy*F?|wOR`@QR>=nsOo^ko1^rrF0+){tIvrieYMZP{kbLJE@ z6A#w1XCyF1D-Y-J$I5%Vpb^2;t@s7ygzu-ta5Fr1rcuYrOftXA`knNJs|CXYcQ1Ow z4fZoyOWLefjATjF_a`_Y5GbGZs_ENJeXtS#{hOhf*`gQ9DF> ztizHa@Cq6xU;DlD@LT|)qH+nR=2L5u2Jtxy1D7KHbD^0k9{*Q^7|z`)h09d)QR_>Yksq+|6*U~)h@R_ei0sYbPg;jpoJ6 zuCh0Jfh5mQzopEpfQp6_Rg>ZmjE74RtGz$u<>yyXOr(#RVlgJR^Fxxw&)T@vhUKQ$ zU>=8!OhsYj(VDpwu`(b}9=(=&UH2p`tg?Z^(X#FOIKWp4m$-O!+x{pw3VACfQc5Py zbD^tk>NmhXzP=Q9=XwRfO9i4A!>mlkNq<fz*I5WwXU1?dF0m6jIjKa^tl!q6G;z5hnnpYvAE%9geGfFz{1a67u zt3y21h2}X@!S4E#u=yM$3p#hpi$%82cA`S-L1LCT=D?U$N9K}!ipEt*r;pD@0Zp8% z=99!6cujJr2B}mOlRB#7Ig1gOB7Ppb$E#8*rR+NV{GzF;=$h@#Ic)=IetBLCgx3D# zT13FsQJ-7!Rs4N6l$w$p-@sOE{d}yT^+0(hv#V8Qlgi>-x1|6#`245ScCASb*`q1e z{SkbM1`{0OFP(a7v3Q5JV77UT_s3^&Gp{Ewt2V|gmN^>VzO{QNT^C+ z&X)Ur*tte*`z8XjJyM0yXt31Z$_4k!eLEf}5`%ZXf3WW0DzCrw%}fLYaGxD528ha6 zMlU_Hl!d-9W<{fRcWcWiL<#P`@+te9WcG6j`KL8Aff{y_{^$={ zvrpw5gA4j_3d<9|gRn1P+T@4J=f3zXPfmu2nT}pUQ+AFXSL{VCp_CLgG^uzkSoe?R zJUA2I2glLD*C8HDDmXD=*<+PNBGV9^4y8?51SZ4c9f=@BGIiEV(YQ|@CA$?=-jC<> z%u(Z)btk4z^C};2RB&{UIX?0CrHHj-_e9O+p|I|d$?}ChYi*^an=uMgFANsz%v33a zfkcTd=plTRKv?d=C0I$)yvLtedoNObt84a!6>~vRA1rB|BtiGct3|YU%ogyr)HWSu z5$>|H`dRE}XbbE+*TqQ<@EYXo%JW9!^SCwZLi_&)|eyiK@ zF`=Q?ovX_{e^}wmr+ZYU5q>lJtRS=KIZThkG>oh$Wf3^Hiym%+SXVehgflWSxDOKF zJK5?!I%2I;hnBcv?%16|W^~$r!tRih($f#Ag~&_NiLAJ}v61+*SY}&mQxz-<35-8_ z2A;9cE4GyBq_Q-xJarGzP0x>H zQ{!KTW0^Be6)fxX(?9b+v#hPH?Z@}YDxk-(o!>y23y=7Emu87?eufX$z|PLjRB#l% z-@f=`ui*TwXi>*Y5$3h!UWPB#Ed`u2**bQc=>6$!+amgl1rrmdW~mKlf3p zMf;&CZaJ#$4RF2+z>bvEgNX@1LzH4Q6lHO>ImVJ3e_|d-N5^qk5usdbG+8B# z-w&pYd%frXy&h+|+b&V!r)Q^EX&+aX;ne3eMHK(hY1IcBOP3;enhy-c$^k)xo;RB) z!Sz{vl%9O^CCZh@papK?pvv;(d$|3{I5=c=rtyod@#oOP;O9;|+b-8%vwl+ihtFND z?e2ftE;r$qHNJWaFi3k=93k#A!9V`rj82|_q5AL7{`;^6I}4pGq|Eg6^hO_lfL10Q zHaBiS{hw)>Cv;ZztIP^0;#Gyzh_d^TM87EVEV8bi2yjTfP_B+1a1up@Fee zgAAzx`bwjQzljoDD?h9z|3gT4CM7K`U=^R@5fHDVTt{67i;9q3j!=7h`%zFjz!G|H zo0yoib#x$|6b+NLCkxUtGJ?9hyRpj!=*1daudFx^JUqjXVY~`K=f)g5l&1{9HJk>c ztgCJ$$A%v=0zEm~vW10(SDBf?Tf{Gq<(P!y7h2=huvn^^*Lq>?NT`|&J|6j6+*+su>5+Ea?rxkQ)zQT1-J6C;o_2GiC&>?;!3H0Tp zRG{y2A?zlFc99}DBt_jn-FcOsem*?IFz;ZV*6GuyVNBKF$&Vd7_D7wT;$#H~!!MZ= zmzd&$s~uo@6c-mq9=maAy_mA%m4f_y*}=xxr(kX{o1EbN_ zjpFR+&G5vJ zUkdDCYh_Me^A!na2^UDqz?{Dx8lK~b{FlJ*=LZscaxyB=s$IgKlBS=9v^2~cqGgWW z?0h{8%V2bxWQJFQ7Vy9G^Czf>1c%@ksYhLgn_XF5FxN6NGA*5*k*qFI-+8${u>dY(@H+PcalX3Kj_wlrDV|71Hgx z-dG(4>4Li?OVC5OsH4+*jkVw>vU4B{J8t(2ex3A?0RP1+SGEy3OALkC$No2qh>l(C z`VY&EuxF~q3n-=&gu11mr_$D1Pl+fx9}`)zY4ATjS~c`nCt#!AJ=GGdtEY#tErA2R z5|sP8O{`(Xd+cm;qE_u`%_^Nbx3SjM z{8r_|Ek{9{wX|3pzUU4IW`~r#Y^^k{P zO!`4);09c9FUtcgUp>5OUf_&6~NWL;2HslkL&utaCukbO9F$an{)F(Px z{-7r}IG)^$Zi{I-~a=PZ3$>`ue>SNNx3!X-?i3$n(d_iDu$Ot^#opk|QW z^WvT<7?xz)xjM7uOSj5;X&oJNmRV0rh_GxEnbV;6B2ap{s~SvhUkZCai_ka=yFA}3 zR!ZJkQVVh_?->{9f|i+fM%2D-U3puImysI9l&!c+To<*w!);)RMbRcZ+GFEyv-F1D zi!3O8ZmGLt=Yx@)w+ChN&o|Yn8SLB(=90wmxjr?bJK6v;G4ZC-X=<{;+d0- zDkTBq=JM#J&NWuIAJelM=l@E1{}Cqf>rZ)KvN+izpaqT$}7Z(SS%Ht-|Ed>En^jfYEmoV@ayqGiO< z_#R>><8K=E&ceySb5`|~?m}{#Jgh-Y%){6d&ez{kgFu`P#$OOQHn6KJrJEF6M+`v? z^ODG2Gmv&H#ejK^J&&J$N;#hd>N`F3Vz3WqtI6Q&=?Zj!G|<73&S+HegdqzH3n##c zH7tNEX9BbeYhb@p+OT0mibLN0Or7gxjlBWr_#ZW0e9MPxrcc7BEP4x_j@osk-zAqT zB!Jn}>w$$dU3`Ky+Ai(@hqJErFo=g`zsHi=+A2V)DAFwsbn4ENrL`3&Rr~V0DA}+` z1-6rrzyO^S+Y-f?d$jZPlstQ|&j^i?M2{O8qyLue^5M}=BxvYrX9#9{w7adneal!b zmKr4I+G(;7Zsc$_NkGEb;FijkEnBj0eSYWH*|Nt21PIAe`S*=s6l!KF{5MV0L&u!! z2>PIn@}|xf;+?Cc??^RFWAWItd%WEP)pL^}c;h230{l*(!n?N>jMpJSS2(`YVV_H; zD}cgICnzF=CEf`lFypfKo|k#fp1Z%Alzv60&BMx9cc?z9)nWYogAE!-Ibw;-G?1HqvBva3*KrmuNLJU=Zbd-I%j=G9WEzDX(xrEPFRkj7WzV$d5c_ z*AO^vYSu(*5vv1a9{*YJ1~ZT{&%awO*P|W;M_-bok=m|hF)W>{!zZYq!tCf~CP`ep zEro5>lp82}w9^9SY4YtFG2{h5{i#2;y{+xZ+G}PY$~m5iAI~H0ZAIcH;D^y4qoAfU z??=nWJMe&6+?|*&cyILv+vyyAQ;$7Sjl4ejdrG{9Xj#NKmIp*UU=s6$1q1!shK%xW z9vCfsbI%(H%$G`NOjvU5ld=lwF|-nP-_|QXuD{mpzyaT_+Ch;?dhEXvOO5naaSX9P~oGb!&*{7 z_TlSKxiXC^#&NeLq@>)4SRJ00mewY4%35I9jb;7dX5^vBDt?(XQnG17PHKvuFEe99LD0-*r|9VvC5H;+wtaGj!& zR|&!Ak`#*firm-9vASCu)kqZiCCqu%^ClIJcf_A#Pi5_4N^=_J(^}ah>9BwR29@K= zZ^OgCRIVee7O5%272Q_ID#*?KgJw-(fZ6r-tsbqNr3E6t9mMdE* z6UEL|>JlkZ7*+25=gz7%RbYO$!E9@WTbW8cD%w{aB+S(mUB6ArGC4R;+|u;H@7W1n zi;ckuiZ7x3H**L7`+BHuyt%}4cJy)kW8pdS74Qt4;P4v!B@EmO;Zapt{yOVsV{7Ejl$&@8%5-i^+1ZThc0b>jBpn5>;Sk`oWb5 zK|1B|GJQknV0wPqw+aHYtsp;7%kOqp0tF_MzN9pY4vYX(@kfv%_L7cH=-(&eJqDv* zWn>&aE26&LN!1ny8c|3j9^=UdFeY&@`FzylwYMANRa7JhbN&0p>o`q^5WwI{D;m2w zz734kQ7|VUF#YE-85!i`n@{#vrcBra{(!<45bW#j)i#nk^~KK*oa=pLA4VZH*ss&c z(K3VtdP-bK10x`58sm-YzLA{gk#0iHE7=VeE09euk&Ff_4mJyU%*d^O%igK)X3|8T zs+VqhS?mx3aUsZl{==HO6F7z`(P6r|&J4Gh$ZP4pZ@6!aY1gZ9D}`>M*BfKH2Hm_t zf;qO^S|7hE;?R|kg9~8tZ%jvTegjqfcWKZ}v=UU$EkF^g1;WoJC6=)w95SFl0ih() zDnmk#CLgVe${4OjNXpT&SUX;sY`O_8v$!8YsAOX}BWq^Hx^j>oQpZM^6pE;mwBt=( zs=IrAt}*(`5>iuBTR8x1K_6LOEeVt2V*oPrtWO$r?Hf<+eFpHeNP$kCz$HDsEt~e9 zhtAcw zlt!0v)12#?Qsh{2=1^0-80cR;L>|+X%kXsoglwGCbH*Riilpf!3<9!?dtotludj3f z(-G0$67`Ig`54PMquLAVd*it39%*ECi=|C!dyMv#uqOdlkXQ80Onvpr52;&25Z-@} zPuULw@+4*ANYYct5Hx<^6?V%H^3o|f(qcX$dV4{cib%csRns+$*it8@Gf*GWM9l_ zGhN#*s_$uy9eEu?#uNyG2IYBM zLK(wdUGnO1Pl2Np5{S<&ad7xcUf=aXS%Q}`o2`=$B8N#oe~c3N&81-MwUc#liwH3d z-uX$~N=6v|r}+Sa$3bbr5(oGmIvlke&Ghvn*6uTf*KPsq!^ATfyjcyf(t6J2>Vxt`$ z4Sq%6xVz*pNFrI~|&3<^G5jXA{ae%j_N#H*j?Aarpof7=UvO zu5q3h84S@BPF&8huXkXwo(bB0;4z9^`rl#RRXenIwnEt zK|)&q&l`R1bsgNeKrt2ND2O6^QzjS?tAvh#Yv6ji70yh6Yebe7W&_^yDjeD}Tu0~G z&TsG9WDfvxicWG;IA-%Z*z476gX_Q{eLMm%tE&Q2_SmrsIJWznPxA$FzZbkXX00NO zoRCRsbuu$54M3_bmq4RbIzVU|7U!34$t!RE_|2fBt?dgYFnEq}fr)Tvs=mH{vV86J zK*6KCM!Es2>7~!sZ|JyASx2@9-#Mii&V=dA8?M#;@VLBLL!e_C>Xlvgng$-QYbvw% zf`ic=5;*2_-6TS|VSZXMUF{_!!Ot%(vpDsWC!7qE9H??(Xo)$3ca}2WE3nz}kWv1j ztX5~bn>RSQDfB7(r3c^`xX$gmi!6DR1A}k_e&otkU^OB@8Qw=7MyZ5!xFF5*Im2B2 zO%un9!Zz(2LQ58qEX(Djrw+C`N#JDZmJ>@%e+4M8oWAde+=7cCBtN-Q+U4DFC{kgi z1On7~2BrlJ#u)&9KaNjVY|^e@2enkPTh|x5!lEg=bf@rQf?DOwNUP50rf--h5)9T1 zH*s!|N%1jtFkrx)&)we!_?J0=muwc|;XQd?ZLcn(UE4MX2XECItGY`TQZO{G3Il@= zv`bRKgSeb-@DK9lhJJa!&ZG%^?7OUf*ixZZ($OqYlpI$5Lm0Xl?(X?q`dz^P?QMbF3U87Bwbqk?N7+#Yw0XxyL_k6)h8hI6HjaTdH12*8lk zYs8^C<)+L+*iNA%=-p%A`8q!|ECa%^VC?xMG&$Y=XU zT{OyDMGej^$cvz@RhqqB##VQ%fPx!$Et2Q}5POPH;w$QDgMO7w!LiSPxFN(b%8*RC z^nB4Ac@vow6x#q~iNH(n*YCO@cN4TkOEEcmy1Fj5 z=b^+LrIu12U86fm1BMhS@TO6j(~DKzOy92vgD6}5EGHQ9Hd=>iqs39vkd~vh&BlO_ zuednzUgUu=0Kapw@{~Y;wqCx=10hsR%+HyiK$GY-6tEdoZI23rfJQvvVB^sP+bYqXt zi({FC+h7y)qzI_F9D@+(!FqpK1qxY7y_BTr=>Lno_l{~Z>;A==qP~jCU_(cN5eq6! zV59}ejH2j>pn@VpQ;Mhvks5j&9Rvko1QC%E6%i2x0U)zj5_r9z(jD+yydCu8q@6Y~x_SxqF%5#B#T6YTdiu7NaMf_5fCMI_4 zG$=zrV7X-g4ni^);GZybuC`?MHlKDlDi-n5h@>yb0%1u0*XHC1o##jQArzY(#9u(+ z7)3Hb#Zg;?z)6S0ZRZsRBBu`duUorgYz4%llbswE9SaY3XGO*NEX!vtVBF}!Iawxw zBc=@$f>5hBblX$}qMi!iXn3TDqMJE-Pw`0QV}ekCWFc?o$TmqcC@V=v+re)d%J^>I zBn%C;kI>$Uj&C*|PgMtJgATDhay1X&x69HS9<6#K&ysbTwfAxSlp)BXmIJREjl&>U zJOuMV{(71Cf_$+Ts2k%;tlIlv+~trto#b#oy}51=Tpb$0{sna2wko8L4wT?bHaa35%}xfK zI-aIy4~b8rK#6%laTL;3I5YRQl4X85O2^&uNdzv55C%$G<%50BQCt;uW))~F z#;qdF`y53AZsGb#scg;r3yFk?1w-@7w?*W>m+vEEiW2lXN_9>vjL1e_RCkSd04-!* z;rRAxi_J@~8f$xh+MPC^mrhiHdWj|TCB0>K)gNuEpo-+Q8sYCEs~oYZxsq?i9^M^k z>5}S;wOP+L?3T={vbGv=UxSl&KMbWeW03?!uiaw5@bEO}k-MAi4C>-iltTiIcFGS6 zMiL_LeqBNUW-fM?^>;BR`VoEo@2p|?I=YEI?Eh@Z_df;A=)+}=L7j!;DNY|WD9>xP z*$fV0x7QIkM;s9JeIV6&vm^>gy=duxuCoQ`-926xcQk+a@B@kF2Q?Kd7lqB^P!)>k zx~pHMBVs$UAtgrgBXmQQ(esP^mx2mFCWuTiBwkL9GWSDBj4gpb)9-iiPtl)&fctN{ zzt~H8l=LcO7RcoRO}29nkdC75tgmH`Iys7lPV_A_6zUZWz@@5DK=c72bq@R9qD>M`w<{o{)WF)C`M;*96zf;mP-}e-8jL*H2bBt~xd0t&S|5S@knh_4pcenLAdZ^~K1RgE zTq@3uykkLutO9_fB*dGW`hg4OKv!V42*JCQ19aE~9%XFG7rFf}6~$-_{GY&rKLj`- zY91E&Prrj!3^fk`8`&+84uuDcOCb`eqnibQ69~D?P(V%^trkU|7vKEllnXpHSfl%%{sb~ zV|^se8w#g0LIIQRVBfxd+hwUE3z!CUYCNdu30kT^^u{a{Cy%x_1d9B#5HLxkNdM&z zo1|7|r=J@D5#JDmgiBpe+E-aT1BDHpOg_xdT6ocAX(rTHtV2h)XPOw4J7Kx)O=m#! zT2}1|bKdn?<=3ck2KLNtAjTXTazm+*RKSxaQK=*vB}l=u;fWyOQQl}45qiPZH3ASB zDzkV)wY0QMh9Lv%Z+3+`9y`Ezs!%=z2Cg*c3u!_CP+_i`7apXg0qQzoZcaK{^%SnN z0Q>^H-_B5M9yC`Io#l8Eq-A^Jf`%T2bXc*H{Fz7UvaAYo>b!uAiZUVs0;~3P%5oYk zy{{kW7vHMh8OZ5w?a|6l1(QHwwN`4y9+YcCVFs;-ZwlNE!1p7hp$H?OP3p2yrPLPR z{yPX^dsjIltPQ0)X66^*;T}R01JP58zWM(FQT-rTJQ{n%Y9Wzx4almYAY-8-fgBnn zn&L~9&@4R>cPWopor8W05-OGk(kT*-(4Wo+z=yce13Bw696vrelJ?6eoVaZdk`dBQ z%54Qxu?Um^HtA=hED}l#ls6%}d7^%$Ji@g&ZgT$9b%`j|_n=i%N7ogD2M}F3UPCF5 zSUUwc8|u8aG@MgHBGhfXWlq+C@(5;SqJ^fv5Mt=i$C(63*U0-08X$Cn3OQiA9_YJ> z#o6aA4O#3>;IX!^v;r$Z#%aj{q5ijEXpi6rvO%>IfNWH%aqcw_DQ}hbz;^ipyOD}L z7JN4rG|R4BBqS>WGgs(@^2o25LAHj z{3&9)0U#jB-ei9oJVN^p>3g|lFCo9k4w#=ZD*;sMYi|aIfM|?Ur(!bJN={ar1gW)W zf}gUWS%60{dWuv~upod%Cr2GZ@)2K-NMcByz=Dr8Cz4%Z2~ES@7be9yzk<4jPRP-> z*%|tf(;1L4{s^Cz_RyV~_b$P7mT!8Y1WCX50ObC< z^=A4grFHFe0oiBtBCZ6cHz;Bsabp2ay`FmBU2=J;74m z7ol`d>{HoqU!^=}Rjxp>;4AKYAtNc?$f>A76q;ct5V+Hme{`bEYsS}mh>a9Zye-UG zIQ+sv&=VrdY*DRV&4$to{KlL*ndGs*Jn--JE4M}JMsel=*U8x#9w$M}+(ZNtFTc>$ zp#0^zvZ&$2!X!>N-j+OS>t4OXT8D0QT-$@1C-qCj;YjXGt1W%dPf`*IYHHna54|kk z%y%(cVD?;=GSsc_0Y|c8DG^fns{#M+F+A$EQ@g2=7-m6JfVWf!sG2E_+?%1O9BG9z z9bC_)ASiv3W{zg$)^|ACi%O{$=K3O8SNL739qV%rRDky&jN~FIE+c-pBYt^+icPT= z&NdE7aHtZedJ5I>#L1|iHwxM3XiJSKvScjv$EbnowH?|DT5OWgo%kjGoBl2?qsi37 z1rLYGI-!bh^XML%(OD>loZ@*pRGy49OM5#b)6)4;35?9_r4;O6z|xqELhG|o zNpj!F34efj0b7`JJ-hJN)=clD@n`C;=8pK&fO$MaMj1=pp!}yOFRAuE=op6O5pLAg zLx@X`tN-(X`meoU%4S!*;j^keU6+kQ+;ms~?S15AVEdaHE9oUc9><_W4;K1^TI@`d z={Rj|A&2@XMCM0CE|7(u`QLdPwh|z1NK>ZAy`D0}E;MWeivs3zAUF+8LH*Yo3fsPu zb}N^({+;q7qM+jYy-0pDa$06jhYU=arGPQ$IAKn=ev#fYVrPw1k7>yG?;1gTQH#@u zn#FB$dM)ke2FJW|%idRn_na`4j)A{!#a9AZ0OC_kU-jwBGX3T9d? z**cmkXI}n<$_o*9gk)_sS7ZdFzxUL^Pi@P9f=n4yxBfG1xgNkWow!WM#Ggv&3_Vn4 zIfE=(?ddJm!)%E=?rCY?)UEbMU#Rq>KJbAC;18NwTLBjaOWEB2odDt?Qc$mr0>%4e zMDgh$BwtS=nyrDF0Ww~>l%2>bM~+p_ZmcaE3LDS7Y{WD9>W~>~V6L%#L&glO26Fsh z02L$6>GlKfV?^5as3JUua>?{ZaHajOSt!4s)%bU~?KuWjDMKF@+P8ifdp!x+-7Ojw zV&|QOjFgm=quh@6`hvnkkur%!P9cxOv`I`2x88DScmeYY>k$>kMNxAwDo#Yh^WAyQ ztviVWOXIi6!9t22uJ4i}KPE$j@Ir(J;#$BXqq;yzNfbYUSOAFa+u2V;C1vBYMYmLe z^g~i0io$t3Ksmd;E}~I4|9iI}8g+4m%Ep6|r1#fSytZpC2%md41$84$ZoqK8q| z5{F6^#<}1%o$*)%KJeZ_AM;nD8%XW)jnduF2m#;aX86W_JRFWS(jV@yK7D$FiQbIa zlm*C^i223EcPY~=zOTin0FklXK}IsoAC);?i2o}2cx`hQAkg>?w_NgT8^lUekwR+B za)Rst3R8)-w%+ZIY1CX!V2u*#m8FNbW)JvwQdluHO8b5vNR7#6=v5%YD`F5fs%KfW74O?>|oC8S~J-@$)!8{Ief5b?4 zkvo28i31c{$;e5=~Qni7F%m^}#&NCURczUjfB^P|EslEz(|96yv4$z5qjl8Ij7! zgFCB?kdCjVLWQ@=6LKy*ZErlCcU}z(opVrabO96i>*x=zVDBNnwkHCt&vK|mdT@W6 zl#S21H!}#INJ@rDsTjZQZQshioLG_smWAr#n^ac>6Uj%RM8sW2GB~RW$v9Vi0mcGR zoA0lpp{ZE~3kGaSP?5c0A;rE0 zAYwZk*!J6~fE)_@Q7A&n0K)Vrh`{X{jQwjFX?=0G1wzQCJ*@yzpR8rUicbbVfvT<% zS)U&VDLkMu=UCfZls3r8HMeV#``q)5s`|zzak$zJauO(hVTB%yN<>Yq&T_)T?h~64 zKVaLRynTo61(e5~Y`}WEd`x6vMI0H2ycPk+$7RfR>sJWUBK-<()NoOW*}=ICK=Kk< zA*eiN9sXysR+A*3C@?0O6ZbrSwmJ*o#swaGBMFdOoTK2ZK* zeT5Ho4V94P;*Tg5u2>Qs2{i7tQB;wQYlp25ErX?GcReU2vWI#D@~d?BLqJJpoXM6O z=5@8TZA9SOBKQ1`QLv9gMEyXN-5$P#8%@fDychJe#3BOX3W04kUNY24cG@O~a-nOF z&^bXr5x)}j#r*#Otq~O;Ag5M&dvojpA~nDfLd|qLLg_%%*+FvYYwZ9Q0*zC60uF_> zw+#xJHlxG|7>FHjnl7U-1Q}Kpu;0Zsg`mnl;-}M{(C6vS2_lY%> z4Y9UGO9R2MhNIoFhg|+&bV}a(lL!MgD9bCCR$O$YhrSd7PKHxGs zQvPy1&t*us*kU-91{<(au-P=ExTW9LY=>*ml`3X@TawuYpC~;!8 zj!}!YtwURbWe@$EcS>Up-RrcL#sl>BcIMC`K=%mPRhH`iabH`sGrf^%j-D+(@Za)# zcnQc5`7fL1ML)u~|7ov>-3niAa}irN3lDc!v;#)$LhRS3gFfe%|Ij_+pY2d>v3ag^ z86_ox;#@nF9Lu8p`$gpviFKB=n;e?I3a)VU8!>;9W;}gQ0c?_|HRLh`DbPX|ea%{Y z8JCyhd9Qe?l`TOjpPcqNNrYXbFI1h3oqNPiF2vphE_YA~uLpE#OAFn~G_~u8ZI|j5 zy?jO2cuD#tUw%WZfJbBz0OleOg|1 zZ@S|cw*_0Nxzi+EiB;Tq{@L=Y0qxXb^A>?}elgql@&!LVIk|;X(lHtMCiw`UEd@?;k|r8DEZCGohCC=$8scMh-Dwl{C)m~}VU{GdCt-OAe(48ixEXigT@B5vEi}Yo) zZj*yFuog5@vF>2EmoqdD=uY8xt1;x=>Mfi+H(r*(4C3s@?o(ht9kQeNQa1I-%Zt@2 zqeTH$V!tu!21&i6^an|Pvk}rBzd~n;O()#my=h3rvxeF|glJfOSS1|0nHp#NK3TyJ zVH;GA>b2SmBX6hZ#{?dNb`z87T-HrETP2J|O-S^+Sp)4dZm|elg6D3T9DyM5mUCUH$m?j5(kgQ+XD^4v)8+ z7)~905x@LRX@2SMr*#Du7Yf(D_nQuNv*iWs$>=&EU%6ic=L;wC`0cgf9kZ_`vs;XD zcxaHY^VflFd*vU@mbQf{T1}@HIM$C>2l%Uy+p;t8YrIP?^LAvPA7}N3V2hgBep18m zymN04CYzocVA%TX)?Xk5zB~?^k*%Gj ze%b8#OSP^bYgP5MSF2U~@DEL@hJ@!$DXde zm+>9D=}>h1;fkwzCVe%|QDV;J5f7(oWsOvxd$}Vz?Edj3q=)CApKd5W43o9=ae`uMaiK6!FrpR{YNbQ`oQQU6Wv zd$HYhb+{^PY`knT))tNq_jN&L+LrIa@ht0(R^rc;rOlTP44epj>xRjr?x19ZQHT@G zCy5=(npw=!s4e)#b`?p#x3sUW#!?uxrT~7j0%k_SLEdxo9tYwqED=cR0Jfdo$s zI%QaKf~X}Ip}k-_8E0xzKl)RlZw+RWdmCrq!R~CBn9lX(%D9V~1k|@Vc3)S|!|}>w zdo^!om9tff*Fh|DCJkD}hL`NpW`s)jOk~>&o|g5sV2jjn^V=fz8qWE+3`D0#4fjRx zM>SggbLU^2;ZJq=IF7R(56=h>Cw5AP$jYQ9xt~Nnj+tLrztP=qMDRX?G~Fsu^^AQ3 zuia0Zn|c5di~Px{uxgK)Z5eEnzwxxyo;`@~?T>yA;evP*8n)QMxLJD}$CHZJ!m-MB z?iAPgEN1p8!$7$R>}*zY&eVs*)jz#0MPb3MCZV(Mo;C@s+FM5}ZPv*rYE2wyUaz%{ zQ?b>yXgVs9=Jbd@0RfMp1O*c|s&mt((eo|io%Ud#73i@eh0X_%ODc9z8!cX7WmSxr zw^WL&XdLqOWoZqUk7xq<5P&{5`rKQGPXy-YUs#Aw6 zV_VC?^w{^X5IIAr)wO}{@2jtO@RJ;hjyWY=Z)bSVor@U{qIUSr4hl+UIu@S#Olqe0 zfdTYw7VcGDoz6QG(iijML&^OHnwGtQ?k>=H|% zhjSA}-~)mp#_q6464rrceT!>5Y7?~IY5s$gNvw;c|6IC;E}Rt(<^OdCh179Yo#Mu# z+jFd+jv=9$y?-nA0XJHM6xr5r^Gdx7z7lz;@kaybS@x~P?eeJ|b4!>h+cds3$5_Yh zEX@}OYn9yD@7-opd7(EbemeAWQrc*_4S%wFcc}>NEAIbD0irK11@=7`Ry!pjh{F< zWfuamC={IgHsq-B?KQh*w--{&LPNI_`L)Uzn@9BK8-}r{ZDqwcUQqmry|oklTT@U) zGF9mf4Mm^=dBph_HN2pF$IIj99eT#F-0i`b=RznH4HdCsSdOX-?0FT7YCb;;z{8t~ ziMIzSd*=KT`59UeM^xF%8aqptg{+cJ&=~}$IjCpr-Yt9`b*+XqUqN}b`C~W4p?-bT z*2=m_qma=8VXPG6^4pv2H0WM%jUZg9+tc>!nbvlhz_FXxILFOhV1cjD&#Hn!u5;q` zXVKdR17o#GqgiAfR?1%TXadTzU%J~LfQ|d+TVzWFSvD!?D2<`-|HeCmA7M+j&8|M= zX2MiT)Pg+O_!Ypv-s<}02z_d*fcXG8;pJ63M>B$aT~e=ag+5Ug&E_^T{O9_T>(_@E z7;NX#VQ7rv-8@L%872NU)_Zw-P&Wt zerGT2aK#T8G0IUH11!m>-{z*svinHeFc?Aw0(>T` zhGfCN6FuW<|MX6Uh<8u0&{O>KG>Yz7Oo}}1a*)D$4gbMHsa{&J#JZJ7)*~Ejcdom; zS~asEARxT(tl69F`c)SknT4U!)(3J&e=it%bHR^~J;}(+W!w#<*g~MvB9wzPg;2FX za09n~?8)+eOL*J%z5W#gefDi1-eCAv9nk|zW477FdU>K&W(lhO(`P1BC`NjW(@j{L ziQrX*=vD;Gj_PVZ6pVb`%Klh3`&&s<55Q#6qX2g8Wa_I^AKyB#I-+b3_MYGA5*V<| zIG+QG^N!XdBm#88L9500k5&a`Sz@b&w&kPOqcg46(gd#$zxq;dA#2}B;8%Lv_N$>L zP)aH$w>ux?QJmSVI{*uhmS}CmY!o2B0uEgxi|;|D+&5yB{%c2W;B4+r&1oU{y@rXV zT;ypHCUmPS-wb$Fymw)=^g##pT#Rg3j0AQgRiCrJYwy5Q2j9;y*m@#JsnT3I{r0xi zSlxNciNQ^pdF_+FY=~Gn3Yo|1l{lM-fph@EvvUYSjFfg-Gaz_#uURHh_Q}a*J{O!! zI||&o&hS6|TO_h_sXEDLC!EoJw8ykoI-1*TMzt>RAfr>I9@l?CqsSyP>Nmo0Z#g}m z-A{yoUs;U7-G)Fd&1B1aoUi=UXmT5dasxtZ2mpQ^F<)yBe7k=19|h{spEhjZ%%slg?Ym$Hd>an{seNtmHd7)!h(Yb>@?MOP z0tKtYaYGM6)R;dk|NHaThY?6PvUitW0>kANTVcUVbrw{VGnG=YGt+1zECHECz-WkB4O0PG@7jb-lYWI^f{I@CfeLpDhB zxrvtYJYR{`h+zt<$C~^ib!W4D?SFneSlbk~RZgy6*3I}NHS~cxdw*G^{;a`>gB4)JlqA5QHRie>2lL}}kTbA(NCNk6 zc6=VK@R`=YedwcocUK|f1C+A!sqs8(b(y%7-bTn|t1+ixRvWTL)}}ee88@}s;=NF! zSO5_OGh^i(;CpP1S!wUoBnl^;D}#kFsi3QLIq{0guEzp#C^Dm^&>duq6bQ@isoxfZ zrj>iG)Nkng>JLZ0r1E!-;FH`qCg-aF+pgpB0FbDi1*AwL%p{1F)_n%uJ8SI6XvV%PnnR!{W zEphXfdQTI@{x>NSJE}7EdkQvsym?ma(zzlCqURW8g#1(Ua;RJQ7pAdJRnD$O!m0m7 zG_tg`y^R1{>1m%9kSCxN36tjDJLZW3DA?l}Pdb(T_DtfP?t|IM^$Czf$TMWJw@u&O zd`Wq3ilKgFxkDmMy7o!Iu%4ClTag{<4b zVL_*w#P`h_tTMsIKVX@AHMwS;8z_^gL^gsJ5Q+$Gs)V(qx>D^tV?h2V2@+P|K#LiN zAwTu~W1#DyTfPy#%L*;Xy7scD`B|H(1`CwiBy=*32A%6=?n#|TcP27yi%GUW(*;j~ z_G-p;Bo61D9%U}JzYRHjjvC6V*28qCY(zMg#Ws}nms>xW2n*1q1%oWJO)z0d^1j3y z?Dlm=ux}%b_+#+?D{eGm6dF}AaykK(3;}=2MAPWKf2Gs6%=SM9X0Or7y(m=TS{H=Q zwGYlgO1Vvd-_mhHAHYM^?O)Q>A$eWiCwow?g2OGLZ5K= zfh|Owu`4{m$iwR^lDNog{2{-kvuAb_n)hD^z=brqhVGV={&*|2*f_pD&w-8`UqT_f z8%s1}?*(Wu@-dw&(;Ptpt#=-cM-WP0rRfZGbK^qJX||%I7>%ANcJjdFh(=B}&rYCu zO>-@C2@V{MrO5(=bvgIxSS;3akW;kH8EB-L43x-sT5)g`pA%{&kZpR~cA)5FQvj`; z=>sn~5RV+NarTVTS_km;OGtG=yALtx`h{D^a;dt>b+LEW>2P2)*NJ(+D){;I~RzS zeY{}X(7_itL&vNG_JbDc(3A<5`I0Zod`xmFG3Zj0N(uI&ExBW+3o5pj$+^Me-=+y;ns;-tlMIu{!OU;j6y^!#l0E!h`x~*>w9IpTjg!@wWe54Ak)p|{kdh6}iG+k$@$0<4%^tO7MFAQuW5o)h}xhOhw`e69AxNDuPAHgssL z4XR&dbZ~5FW8yd*>c%pw<3C3`idS6n3Nbo9kKis!MF%CG;zvr`J_ftSvxE$8+C1>2 z_#$SPax4lr=}AGZS*wgzYx|p}9liP(tx7BP$r~Frc-IR2ZMt3OyF1nwO~vPvJ@Y)g zm^f#q$Fp_$#nCdYV~nx-A3c6uz+Y3K?m1eUrWNmN>tBD8^^hk93 zg(HG^A0?n-+xSv{Tc7OgMlSN%kl_8)y!8sgMQ(l&SNpr%%X)+J1FsApaN~Q~9?P!_ z4|E;cE!PXJ`PT1~3G_?NVnJdxo@0Ey9m*Y~dxj8)vum{7?Tj|L)5+jfC!b7a#f3_9 z{2={k;uFg5#y`gS`i2)?aR}%3GB`!6Z+}}ewgL5*mPgw`-kkg4NS$*4iskLOZ zNujbI-?l`AIgbCGkj-9*bFFbAdL2T{t?94NLqi!!1l8pq4f^rcwaoXH8HhzD^0REq zlOwTrr0&z!yO*7bQx5GONXV@}yd*+O$>yHt21HfB;n0>gNv*-!a=p-9_i4U0WG@9w zXY)e{J^|vd&JW(-TiAVYS<;ousUv#8gG2;}eeBCk-%mohy3H>vuDVkdDld=a>;u?C zeo{Hxg{LM?-WE=dH)&t)DArzXWCMY|MD5i|?lnAZ-h&s^w>d>Q`54MgQj{Onpcc-F zt4|@$xeM?!TTt2-v(IVteQqy7I8T+%1hm5zg&a0=Mr%%&;(NY>bFqOTS>f$j-#wdT z%R4%yd3rWKIB9F4%I~`?GkKdw(Vwc$k7_=Rm_%vKr#FtkanfIr$NgC;!u$XTl}C~$ z{jZt&C$6Kq1ZFoumGjHUjriKOXIh^=X_0J?g+J55yw{h5*%0$rCp4h)H+YNrF0f`n z74}$!l+9(MCC5N@(O7>vh$Z9^UG7w~F-w>rl*4|7& z^}82f<(iH%DTJ!6O`9Ni0Pgt+&QicKY)c7{#pn^fYD63Tq9 zoN?N&wPvHx^>$#OB>IXM_=Uc%2f2uJ_x7_z`A+Gi#9B;AkSk}%ZU)?9?LrS;M3a<= z%f1t@a16Pp{>_Zusj}pwN3Fl*zGRM_@4N>=e;xF%*1HiftL_FXK59TXxj?wPFfG1k z`rDf*um;SRC-vT{sj8aaRzEzw0Ga%gK`G^hQ+pCH5T_F$GP%_@5PwS|Bb+|2vg%0l zaXdb?b-Y$xkdUh%tCL!L^dM9fQMOb61X|AQO6Lj2Dias(=R9*UN)MJ^NKwyY8ppA9 zfc!{v>QnM?rE(*@pQKagy11@5@4gfC(1zrCh0k4d$Nu<2?6x^W9GAov&0`R}+F7IV zb4k+gsmD`a+aX$b&z}KcA-!C>K$cvr;Np5^Mm;sjYOj`6MvjagdZ^U5<_bis^{K^} zUCXzUqy!5eQ(~XIHR31-{1l-#IJCVB1MQZtb)Gsc^WXvf!?bMTie&@dQC%_LJcBG! zm6d5}4_S8_cg$atdW*{}%x!EfH6hZhSLl0@(6n8Lp2#A_1JnbR3n*)MNF_BuliN^%z&9*kte)n#J=wrpnQ(z~N z47UXNkXm$M^rwo$LtP1{fo4s_hwpPH#I6_$8f=OdckKkh(f1r}KtbjvsB&g%1n6=u z?uwb1vp4Nd!+UOHmQt#V$`Dl=hrKU@lCtOOQyhX8<~)Cb?BW{B#Tz!CAnqDgUu*RK2U9$3a(czQ95Dxc^)Nj-#$VkN#I8MlNv!C z4k%XWUwR9BO4`^O~vZ?=(m?tKtT{ja-juSO2*#yU-doNN z*_(_t>2FT*(?F_HesL~a#qHgy_ZqtV%MDyVp6%wSVMut#JzWxGxQb&DKj1UFVsABp zoG^SpP!Q_T(LGy`AobKi82Hy_hk6a5s!iES1fFT?rMe(!H62WFx~+rr+4F>doz-=J zApoH}4|&_4r{XKky)70`NP0>7B8WeHf~3XR7V0X@`jPzspi3XFqo$Lyo^{9s@+jZ^ z&gYIV7>8~uOuQ*;weUe%+t~6=FEzo>J*3k{ZT9iC8V`(y(zx_EWqH}78eMUG#IXS##Co~ma*g^V(O)QO;i zLa6bwcwov_N zMF#*fX74Tl7)7DOi|e*xNf@xkKQ;TlR0{sK4zUwAl5?!_buqB>p-3VNDi4|h9%T~A zAAwCfw&bpc3vCMm1#i4OU@Jnmt~2RIT+q)P`34YtlwH{1=kj~9Y?oaWC5^S;Dpy->0GS!W-1 zSoZ1IbfBp1yBK%P-wAtWiECc_1_*7>P3ed|hHn1s7Gw*>uD*yj6A8Gx&mdXmBpX@) zFI*c%ota%_qX~0dXH&MOQDIumuT2pkmBvUNCPNOM*pZKxK4$P*Q%=6lJ|_$}L;Yk` z0;m%jtuBB9gXWSgqvv65#B5ib5e5%L(TNsuP|0XomG)GjgYlmXtf zI5rYRC}~$bxQpMO+FH_^m|>?y2swyK5U#oc95&Kk*HA}4qU1c1i zgmV;Z6F1_i>0wA%8dPE#ARPzEa3jMEJIM6Vg`#IX4(PQS6>mkXCX~6vTvngK2)7`y z){}(N@#2LnAj{DtR-v~aZ-nGxM;-*S;+i*YV^`f!@1Fjp@#0>W>QA*K8$KsbA;A~j z7_J2Agj20Jqa}Sg2ac0X!OjrXp5|yPY?;2sx#W5~?kZi$!`oA@-inA|H&p)ILbWyi z|0+3twh$=GGjN#62&O6S)26ZXJqi;1<9HM1JH%RoSX@K+rVJ?TZuUsWDkY~N9IDMJ zIQRZow~Zy!W4m1yTaTPy%VNjZ;SK_4W_OU^=XH+@b~#f6NV% zp$llWW_*+d)-iscswq+qz5~ZKBqW%)?oHbkS6_>$6To?it92rQVSs77jWbS^M6aSG zm{eP+hjQ8o-QI~}RYbC%=U+KwBQ=g{Y`~%G1aU_wsrTsYFanhHd?zus9+a>`Y{Zec@YEQ!8JBffUz2K=%wQ^}I;11&Y$acjqTf@KxK-l^%=NtO6OZ68_t; zB@!IQL8vZ))M}vUeD5k$oHL!SvuCLc;j(VQbZgp`9vEGg*~H4(Q_b>QVgp#aG5{sr zpM60HM-Fhl1e%#J&d%a$5g0AbnZ-}`;(o+52Wqnw>v{Q(U3yu{5r93PW$6B_zwaR- zo|=oa6C_9lKf?Hx(slcuHsKg_letT%1nd-!sU8GT1M%uwTi|_tXGmt@ycG2-W4e}EebKVfuu;d84q{2P7bAy%atE@f@*S8@6f^Oix zhJ6*?6s>7c8st?BeE~|Gw72n?eK`kYsc)+v(vS)nw&6EYiN}p#{0!F=E>nud-{{VvLuJM5$Bis)Sfk-H>#vRK>+7o>F zfA_b$>V^H(2l*el(Z3w=-c@*fBMA^u!0^YTsB!}%T{#XBLNvA>&h@warS$(=A-Uci zO4L%nO+aWWH)yW=0%#39pqmGmyhIk0$?b?a0WPKqB$_lXa?nVdL*$7Yf1y z93(QH7s(@QBj6B$LUmM8SDi;)bpag-1f?=6%0*`#-mv{1_Y7}e88Z&)XWBp^G&%K- zd(A^D6V%3dMZ#>M)C%vG+V2~{^p6G0)W9h^WP%!Iu#TdsayRD1;Mlpz;IKof`AV}1 za&o0m1c9=-H5+Pq(fq*YplVwM4#av!F41{`h~b7nY~(T-t9yQN29N0XFQa@F)rD-~ zr+tIcA5&u9j8*OG0Any0a!(h^o{k1tNTK=ypPq`!VySosBo!d7)tBOrs2HT>fsaMf zKy7CbY&~D0Z>~<^FOz9gSnp8x{WA)lozYaP_G@>S(`DqaK~nGgV_ED#$#FUSmaxD# z1*QX8axy?QR0CDKmxYogNL_L4ACXYB zf@ayibpjTmXjlCRJ3lEoj#B4NY{znKDZ4iQK;7a5m3t?9de96Y!xz0iw?8o)6&FzW zy$92{nQa~-{C~CNO^__w`40O|lQJlz;3@C@TgIXllz?CVlLb z306uL^XhTUxd9`+bXyzCrkdk9DEs8~)D5;RFv)KHlCtz<;z8>eZaC|GAW5^WlaK}S zjJeO~UkBvmdhMY*xiNFO*wE^@Vsk^4*MHhMh+612d~VZ!RS!{IWZfr4+kR>!KkFM? z-tz!ZAGsvL2tQzlsAUo_ti3jm9=rd2iY^J97(Mmv$=6BSI2P7p>}0ZI5a-tX3nid~ z-uro*y!LYv+-qM?y=7-(@UeRa{#+>ILG&aA&a+TqKG3VT{$Fp0@P)P!4F`Pt?~#08 zMq=^*|EcW!|K|=C|DCe0r|5sW_uxyp^xxO|-*L|5YdFDE7@3(~EMdUP@;KOO@Z{Lx z0hy&6x4(DT@kHmz0n=Lp_pUJ7tb_%v)ql21S1S&>@5X=IHua>`H?AP?#k=Ug&gr<` zQ)=vwh*paYdy;0m%gxqOIe(X%f_C4L3!k3-)zx&+?&=}?yg!W<4`1?{7W_)Brx`M~ z@L$&6sbl2clkz9B6PyT~Tw;lNP9BB3xZnQePf=er20;UGb;+Ht_OOVCL^0Iis~#ih zrL(_o|LSNK(VN8OzB*n-^vanZs=sQIfL?li?eJIo(?xIEV34zzf{K1Z-~M|`|3^m2 z>UGe99;-ToGWDJ_BE9DIO6b@f8#lu#<&5}+e9x%G(0$#`^W*Md)A5JJ)(6hC5gl5D zZ?Ny0G22B`b#K>~*_C=ZN|=;V8{I~jyg5Ls%#EcDGM)6R*AwU-7*KATs;)xaUC%$f|sIF`@w zfez!N(i?uUyL4#}9M~iz$5Dfr-?6grXRa9Yb8Sm@eDs{ z4IhY`5zhHX`o#U~rGwG2V-=-zzC;{VPyrnzo4;Im@84rsAZ)GDe_$aTV+1L{-sWg@ zVZn5~NmUhVJ~oc3LFcPHe)Q-O?a>?U7#zQ_b^(20RfGv)Cu}Jjk^XUFgXX?{>xEqF zM0J7iEtdaIc#`!&boXc8ep#zlp9JldkrgHsh8|ooV2oHn3}O%ag)A)&mge?7YFoJJ z?-UTk#ZvOn$FxFcBgza;KYD)bLr+y7fs$_eR_(ZER>`a%FMZs45?Xr@)rq)Lu5CGn zHaY7TBI4{`ui5$84}AI-UBOnsk*J*uXmp`$Ao4YZ$~HmsDx=bJ6B{NwoU^L3u`;)W zu(R1RhaahubvT$~SQ*tJvu@p38hKxIf5+o7)r+@>ljL-HIq|xd(PU*Q4BPrSCb}+tzP{Qo zP?wRW>(Q0*_OR)_n;Dt_o z(kvhUk^_rWv+hK`-y)YP_Bx(XX=|}*nM`1El-e&P-4jPC8_TL9elr_X8b^P^hPFbXdETK< z$Gp&d@fss}D%rg^MtA)BhVBe~U;UXy>aIctsggw)kmwciIF!u<;DM}JFSR82b0)<= zwO)r6x|9)zR(dayKcWN^AGmAk1y*X+Dw*9PY4>bUm&Ial6Q8h!Dy;Agbe@23c;RgB zF* zV=VtVj1-!|*S^MIg4mI3*9?{nj!*=ALTeV6I3m5`^^##Uy2X9cxJc;qwG57tD5W)A z(cdL#_mGslQ!`*!p%VCv*7MmEw&K$9i~mFyZugy5Shh{Bx4c#OiuUO9TU7rE*`IW0 z7e=w6Z?HNWBqc$72Y<9U+dg4mMR7(pPG3FBLKw!V0tYL)%EX4R!gakph4ao_ZFHcz zX7M8!QU3&dEfZKFl$$u-a3grHPakAkK0B{ZN?(QUgw!OTsYs`QSt)8{#b}oBA?=aK z0L5^UU*GMi7q_M~iJQ{!sEHChpXHxleUL;To=2&))y1BkB(JpUjBNQQS?PEW zF8V~jkj00x`t>MpY)FmaHLTz4qt;kSZLg>z646Z zd+gU78-Dy;B|83$&VTa=n}Hwt{4hpe%o-kM8Jb|JX)?tpNYRGC2TPL`y{q7tf4f$@ z*SuaWszK{>O^WyhYdd8StlLMuS9Cj9+X@Rv`A(eeM{8DK0(C=bbD0 zkH5Y&!V)OMPCbbYDOrC%Fb&GJ;Oo=3jmKGw_YL2#zOUzv#|?D|z*jF?S#%j6abL@l#qt&ObAA2XT-runvc0N) z(DVPa=tY0@Uz{%X-_+rMo?xAeP%bUVrfprcB{4Cx>5sMo$C3k!Khqv>S18+(_p=Z- zR&A8_2h{oHeGbJFwl>f4BC-?QH-Py2$;7#v#L&=CSSK0(ym9+e0h47n5g7s9G^sYt zR;(+cOI#&wtbV`OkQLNwvZIFXKZ6^QUfgC5J1U`IkMlw(FDIw$%OUUhoL}N};QbPm ziAXF|>qUZl`W$oNYS1K;KG&EE;W2r0OABV5=mS0}9nMj!Et3|GK84e@rwBv0VW0J{ zC1s0iQ%sE3i=5Xa#E8()0=I!%ej$r1uXxFk(>Hw4vYgB{-v?cYC<{W~=$GG$i4|Ay z`{@AIy$zVOodO#}ziIQwc7;ewe(g~)lmy3Tdc9j&GM&Xj@xZ)iwwe_wg~T-c8PR)0lW^uLk7~V^;zdv0)_Z$J&8YCA;bk~idRv4rHLFS#jz5P= zF)^u!Xh3wQ+Jq|lMUzCQH8Da`rw(KgqN`18XaVQ;DIl*mR)5_x0!2liA4k}XhB=x5 z1=?sDSbnW0%@_mG+pl^IGATk_tCPS55a(5K+EAPLBtQU1eR6*{D}w5ci-a39Wlb!; zn*_n{$_5%glX$CJ$jqHAF&FMcVWrRHhR}59#l=mIpZ1^n?X(Q|znRrb#AqkcXwP8< z1A0-Pcen32kfWw}4NnnNDD9s!lo4pMM@1F-AR>yhBc|D4&o+ zyCe2`N!AR5CX1b1i~%1U=evIcjLMlDXc7bUe!`$)Xn~Io{52@= zTC0qXqk~RHM_N)-Q%6fMVLMA~larGf2iJ^DOkC-Lo}DKYH$+oAIy;lIva)E8tY0ek zdwdypF|mS)k{2}5;${%xaF~rUG7-?05VdD|4Bb+u<^#|Hj;Kj1`~*#=Md!8l^z;CK z@ErfH_6_t6L(QI}p9Sa-kB){p*4%RxC^;~1hlke=$y;AsTomZjuF9Se<+b-?@7%d_ z26{w?M@CXRys1fg*-q4?cLVN|a0-I~v^Fg%OT94;dp2qct(4(}r}fayWs)xFB&6dn zhmP{V z%vt02YhyeLt+r^pZ-+KKPOrv?hC;2$-W38q7wRmJRilH~ZbFMz)RCzKA0ldHKC`+f zqu;%7qpWP?n>TOnCnTUYLE3yKwAYzg%}?+X%rn05LFj3({P6_{-7AGrHhS#-=y)h} z9Xf4frhF0d$cTuDAttjvpcFcr`mt7f>}~C*rHo{fnZMnWfM!VydZ2(!G`S}pyuSo* z-yn_g&iE(5!B|2YVlb4e7B8SHokjhCyW8AE&xH&3J6(d|t>r{sHL?8Dr?XS-<>dy@ z!>*av{qEi2Ef{ab)myZFxPJXQq-wozKE^G7=r0M~e9m_>`}-ZXXkGaU`X|B0O77)# zVHHmzq5m>E(eR9VyN(MCal{twHCd$#mkbONbbZxI;dIj~E{#(P-3dA^ zsqW-*Sk!P_`R3)NtyUh#x&qjB=-7$pYzH_J$qkNY3JvYQk3OawPfwiQr>9r)Nws7= zzq41|c5XkIa1|VOMQkTt+PQOQly66S`=+&P*LrlEz9?$uKiMB`QaLvVThZ0%Em^j* zaPC-!&IWh~YKiSo0?TnEyRF0@k4~wAK?~rI+D4*tmB8%O?FnEJ zSY2B}LAozA6gnJh`AnSMf(_DbCk5!s%FexBW(Oz6pnj(h816c=AA?|NR6vKsK4=jg z*G8C!q&y!-*B{Mubaz*i0X0@Di03oK4L#iZVEo5BN$iyT=;-Ll=|+<(IJ-hJQh(vX zpx=q%fQ)?-^@BPmikLBxoDeLp2ephx?Q|LZu*I|w8C}7IV!VEQsy=;xkv|oGST94wzN}xVIy;W0FTQeV^Zvn^lC7Sj+{k}Up@1$KHI1e~wcl&5#V`Db- z4j&pBdEQ}*$K#D1m?b~g%f6HrG5<$|5~>=E~c9^^^hkB5t(XSVPQ}z3No^n8C&GQ1CA&w>?=YID zMjM^I?B+&ur)q1t^gMk$MmN?qGc;|_@k)N3e@U;y{X9A@hjGe|bg~97WIVN)Tw1?w zZpL?QxUyZ9zp1WzPF3oi{%qaVv9yHfJ!?y=ECHqLbZQk5=?br`Xn5>oHQ*Gm3QeLj^7~MSTFzbz$a4ZYyI2KsMA5YwEYHCV! z)$P?QW=&0{v>0N&r8Q#9WJL78d-r3bndBq~9r~Q+t)qnF3Y1S24jih(?z(Fstwcfh zbRky^vySO5p2?Gpq_;Z+2J+B2Fx3&`)oK2Y_Uh;K1Oyh@Xs7}Z?kDo+l!w;N$x&Hi&| z=&c&Gqwagnu~;I}ACoe-N7^T_Ta)Hw4X)4 z&u`4B7#n13V{_kzh7rxv{cNgI1ez}!Y@zD4ZYami6xbT0hx1}zT)o)%`i;9EbuYNB z&AZW}EmX?;5*M-ie9X`eVJ{R&W zdYZaVHupcp?Q4jqsrh>_D2Dp_b~ageERtfWKRqXN1I&53ibZkORDb(&wY)MsDY6N} z7SQuC^YVi`zYA|B7{%N+!<&(2{nxFE{g{st9)3xK*T`IM%vdohH6Z^Q^`vqpCfBHZ z6>}ZzMmXu1VsF0i)w-s_o78DRr2lT^TKs&&?+d>bw)LB3+sYY9l02@9-Te_hH!6g3 zT-q}j7FP6mbhWL|O3#!q)q5v}soxDos;9&iVoe`r7czm>Te`XgfrjNXGE6*xV8k{= zU@_Ty*aS$`)Yc+!o1QYG%99B~bspJvEm2;_-+INo3kg|Rp$JsW9!>cI$VJDyPd#wH z;Ze<=+ov9`hBFt#{pT=>Za4b-8b`jh!TF#N-QrflLKlv2jFajmFIHk)Q*O5gB_%vE z(jjWK-rL8=WF-fUC?D^%IECJo%0g`9G>wjHuTpb`=oXhdhgNjELT5DW16#i3>TO^5 zJA)cDHwL=n5B3^WKW>H1r)REyQI{~9a&wJ3W~8NM;nnZW*9KyfltvHg;{1H`^I;#`|F2w%1B4hu(7N!&ggnseyQp^6^5bIA%6R0^US0=^$lFGrH(!Ea+fdPFM5ySzBGX zxt-Uj#I)e>W-T<_2ecZ8)^IlZafjmu(I-0Om-Ub;biy1fJW!=vroMqeN`da!y2FBNFEp30Pm?7wT^zhzJH65zw4XZTNh2~h`5CQZ zVeN&4`0al!Uub{G-@nCFf#SAzi_O^&St-TV7G3--bbZ7l+Tf_pI7MqBkt z^Oy&U#%S_mKQQW;wzjr;$a9pkUrl221@o!hZayeUVkn)oBqhU)8-ud$Afl{-g15ndjNxZA!%rtdPG)@H1rYi@nRlwQn?>PI z-joU+*h-5xtqfe43I|9+vIp!rKR^E?&XcLBsdtl;*?xVo8EEC2)WL4<@JEs%uxHPn zyvzM1_*)H{VNGdzx_CffV6%VfR1}osO6obxK+x-tv<#*kL2bO2R#^{2I9GG#Buok9 z$(kL}R0}w=+FPqBym_-Gw_2vAm%5Y=-GALWDIEzu->8s{Znrv5DI|!Z(fA)7kxYGj zaWaytk-l>GO!5(=-w0mO880!q8Z)iPupLfTCA#c}BT~MuK&!^_3(Om&^S&_V;ppF= zNKp^8!H5qfy__Ag3tGc3yVh3S{M;%zO^czCK@W-h z;(1izEKQ1VqGMwPHakrtzV8xChs_(BI=JDddN?erBx?R z3-UN)b7RuJ&USh||4m%d|na$lakL%d2d0rUF*0#c~~x99UJe)(={k0;Y(bz(IfZ zu@_pH5gx!_%xB}5Uy648%H#2DfmA_4G^rH%!U|F6;V(A{g}dxcRi#&9>-6+AxR$KM zELH3o%wY_5UafHI8;-NKa(6+niks!%>l}n>!pz{W7hh^?Y~iD)EUe^~-rikU-MSj8 zn0%wG6CfqVJJF#3S`-D9`7%S z2Nca;Mta=PgY@uso8#@j{q`Heb=^{)ZEeOp`uc~Gk(c8tKj~k6!KoVqR*b2Pyckf>NcPPlvH-a&-I% ze~@ad2u7ZZK2|_Vs7hD0bmxhzJP0-_><1O(C=C}={V|4Lgf8`o)3y8Ig5o6ZdiPUN~gVCf93JT)7=pHzmUpTQFb;vJf~nIi2vrj57eF zgw)GlYj13o>d~V+aMi}Py5i43AArrBQx0|1K=65hR3CpHgk=2WmW!nQ zs{ev_+bJcnj+%wZUO_^+wb+B!)0nv(30ISgi_0G|xz?ELsB9{hJA3vj(w~WiQH=$< zP)yLSUAyEtGw5_1iOEYw$z521dO7&sKl~x^r}IJPyltdIny~0Ff^Ja{<&}o_j#e0LZab4_rKZ*Wvc<%tFTg`<1`V#Ziz|i-{DSSHq>LY4FTY@kWtCe z$`{Jn>f(dFuU{V>Uo$tljC@Rx8qDXRNoM&^T;y` ztEi|LdX2QQ8lP(6UM(PtM~Wz)z|_FdTF)H)ir0#mMolo|wquzfX~2k>G?2de^LW9nU|7y1ft?%ct@)ju4DI(!GkVR1x^X-wH^ zJo&E)Hk-h><2a@OdvM!B@o!==$Ml?xKeN&=^<_7S8s|c@kS=+5|Ji(x^B@^Ki7}|+ z0_bHAoMj7wP=Kf6F?2F2*bT}K6NBOfZ$r!EZM_>WCX}cHb z61j?f42H(2(`!eOwZ+z!xTVAOV~x(LH- zGblKR_lN${0UZOFH2L<|$}~T+I_>@r{z1NgDf-6UAxcr&Z5v*(j%J8i$&4%tWAsqX z;>L{|?Q4_(8o{r~8QaGFj8E2|zy$2(pLtvi8CSHu{4i1vTTKn?cJu91YBC3Nqsm{C zkqmTA!UqGPY%t!!9oOcEIWzJ)Ipob~Yh0T39lI8K&0S$8 z$@r!*L$?Z(cB5@8ZK`iz=t72a1tDOIe37lCcux#da?JAXJC%Z%mL%IhB;MRnc@l_` z=vAhBB5^ax7{&p}Qqv%ZYN0$lK*-IhTwOBDvpJ4n=upGKbQHeA@P$knj0YG~sauih zz477Tqe5#>p{}zEY(nx|QRX^`wvlCBSz%-R9@ls@VD?86Ykm0Z99O}bedkYCiP+*pnU}fLsf%m92mD=3jm*&Ii2HMF3G=#;b(d* zat#su2)%>0zC=5zD{?4h+_8H49?_!Ovsqoq*1*z#I7Tl+SDAn${?8q$yEr&U+pJcLY_-2BmdWm`e*(l`C-&m;pzjE*&-k1N+H_M{`KlT3~Y3J(+AFt*2 z5*&th{tHw6h$lwuTjbYU{?|*l?x+loh*~UyTUjD%eot&lAs`T4Y2V)aAX#%t#xgLY zG0S&x-4~rqCQXWo;F@di$BTBW2OKeHVrgM2Ow&?nU0vN#pfK~b%Lv~jbjp;yKbE$)_Uy@uq@<*V zDzAH*-xjye7pwV~VBX_73rwdaqdsdc#aV7AVD!k5GKI_ZL)9Dt`K}hnB0fKZuIN;x zjD)N&cx{ER+Ji+bzseu2adC6GLP`?JgJPe;L%)Zd9(F4ji%Fzu^I)QxHLwvJ?`LPb zb~4phX4?{&$tvC%jPIXsF-}oa7X)|d&S_Mt#atieY}GKne>@-wb{O`1z`0^F)nuJp zQD$z13gD-6kt3)o{E)wa#5g!Oh-yoy`(*g)=tePMsoE-&wW8>v@B-HU>{N#6~Uo--hr|kZP(?L>vP(4H- zCeSEpWX8syr1Lmq@i43wGTOo^4(o&Yhe7V-xj>)0cP_Bqssn6dZUZnHyFwUFu@B5Jni{ zpdhsqCr*rP1r)(9iU;2`FEVvyDuSk<}X7g1Vuzds$8>HiW*Kb zpfA%Zl9YT}`L84D!I<#pG<7bZm*aTBwXD9@J7?6naJ&kyZS4YI(zK-ZxG<8awFtE0 zU7=u58dUZV_ns_Ge~`bI`*M!cjs7w*hjQ zaIXC%Q$Bn4V`@k?<=(x}8KX72fVGGdgFq<$oHgGWVrE7>5>B`7-dTMBdByI zkX24#u2tvC?j1Wq2s(?Eu@>eARoF}bg341D0xK5+bsD%qR0G=pJoq7{Lx&DczYtkH zjFAcLMtG?_fq(dA-ReiH3xP+3cNtf2$1ZtI_jK@3(_Mp@yzOrQ=rh%qp!1i-qk>Q_ zCSBK8DfQ}!xs^^9J)Fc6&!{QzWi!l*T+CYtoz=(z>LeG$N1*2mGY0w+vm;>;Ab5~mEMWe~Ha+_6@~O4P$9+gr?R!goCNp|` zK>xNCwLUW`F}Tyzg}qq~=$|P}1GSy3>TAE0$fm3Tj*-x3FGXFh4Gua{inPVf;9>4o zT&`#0R_GTei`>C~AwU*78K>pWaPp2=ImicN@mx*STVGeS$U^DUOt|rokJfbqg+871mtUV_lP6VLIywZ9x+b(Sa}lxalLHG$MHKF8RJ;e>0#e|m0co+yQ4UwR zG1RH+?o-hEHP4@a+c9pUrKPQ1n>A>hsJSH;H~(*#VYUTPUVcMLLHR+AmBTvN+WA3v zZi$t8nr2%zg9S*GGDI?oumkYb==Y-??lL#0^%FVVt&b9qEH)$pktbsTTOGbJ1L}qG z?=rfD^Cha44juaxh`(ftrRd%jl8$dXO(5+{4FwR ztvi5^tDZl9UdDUmWib*MK{Tu zBft6^hoxvaYS9(-{yig$NM zg9K{OXza47>T7Aatz49PgYHO9GqN$pu~Lc5>nJZPdt2uK=wRe0+c2#?0$&a3SsmqF ztDb!QXf={~5{1p1hdlr=GMh%BPu{oKReA=d;GObF(yb-ZbDKS()P@#jl+Ual>%@@KhV$P9A+$|l z1|yU+dlEG&n_yd;eDR*9=OUZ!Z!Xwutn!p4I-9&j|0^Tb`zuxLEa!Gi+W(x9lc zr7iQEY)yAA(4VS9Tu&PPHr3rgGP3X~v!f=jxY)O;H$_jaqb69o7AdXqo&u6k1}uX? zt)5IDbuA2zDe3~#J|6T~;ksn9Oh?o8ka(=Q9A$?zcsr5S-BB*iobyDgow$;uK`F79 z!m(BY!MoG%Mzu``U7?FZsFSPJF%@iiCNCKF7ti4IR!Z6oPq$-g?G7_elAXECuqpJD z>}QVS?zppI>^P?|sDj%%Vr2ruMu+=J<}Rs9H-A_I8hi`eom4Sga4fI;zUm12HedKZnBug0=J&0=2ey4Kz;=kOwa}1hr6A$I5&X$f&yfZ% ztfI!iZkF=7arR*rT295S=HP;yL|B}sCZ*j)){{%8Ris&-8o#mf^wx@c@*aBy zy&AogI}iV`ddz##jW+;5>~$ z1Mqu81v9!r!!hZZ;g(ue#T9ee%7x|AHM<1WnhJ?q5D}XoUE^AJSUw9Xrq?VSk$bzi^h0uzz>PAgkb$tA6`-M2cINhj zvzH=g*#Jy&kF(BX(*OsJCevYkBV%6^^T5v#(^2CMB@k`d{t+jZ->K2`I$W?w){Qwp zHzq*l@t3^}hA`rBf)8+f0#6r$9YV87UNi82LwUz~k)S8`U&0S2Aem{BkywJa0!Q*P z$i!;T93N$mIw+WD}2!9Ub&G z2C$yVBiz9vp{ec4ms9#78AJ^u-5?24H5G$S?}Hos>#x%YK_;%I^)}VJr)$a=FvMPT zoTZz`|F(a`O#KiEkHb{V{M=k)JetBlfk}Avl4Yx`rk2Eda5kntB!tjau=KVzBdYJ8 z4+)V0L0#uj^5+#g9Jpcve*mu~bEV6h$GdWKd$i#WJlnWD-|I$uahrD=hjT`Mq)f6n zpt@mxv!U9wR$~#ePNCA7@u~_6PeS-^>$HyP%^Yv5*rEcAt@fYvtlG8ErMIx0X>1e` zauiHszF}bx@m@q&1LZ6&`3D_ILP&mvsj@x`w$@X@qC+h zRL*|y<;YTQ2Qou~hh#8n(GXXL3v6TJqep(Ads(xFSgo?^-eX2d%AWgin8=SF1DP#D zoxUkhtG06SB8Wf_YFcx46bvLrL9abP01(%AR-AB;MEEFH(#W&R-TBqOoFhBH`wA2{ z(D#{3Zp7@1D)%Noe`_4~6yAK}JNDxAV1i=DmKeR(1rs=Nnmb)C9QrO-?QzjE^s8(iD3Jp5-H( z{CD4PQokdWzV(tMp$W*zOwOwfX@nVz9zJ&ObU|cDNF-&#wr&PPU6Z&|FkI`#B0QKX zxEI0@gIwZ1tg|)qtqXo`u)}7^I1@e*{xc+ioU*lwTN=_aQ^(V-UoRnDKHNR3Ekb!} z-%HHL@(dQm>GI_qp>s>s^g^qAq0PjR(c~PwZ|`THeJ0mcjBRP84p(CU^qxi)@z$=N zbq5pXLk)xOkNL7Veh7G9Bx>TeKW`Q1GhJqKh`n+;g&o2_2Y{T69^OQG2MRY^@6%wsc{*u)OWvoS$z|PXf&qV05+GJ+A-ofw%UJpM5p1Kc zSX5{dKns(SsWruW#vHf|*|hkYF4!%(4oMVW5N|@=i$Hjoi0DD`Bm2OP2=DU;?-g$g zMkFIctf%@*O+j9~Y&v8Y zNyjd1ZX&^r5M3_Ivb1P`%{S}UvqT4+@{Dw+^qF02!`7?A=CyBV%hAD6QkuMpf&})f zQ4n*Iwh)*HZc1nBu(es2vVmD|F6Fl%R1LZ9z+RDWif(1b>B^ixH0haVmXs&Msyv=* zP33{*5v?aGuzmQ!#?1-v|j3jTkur==q zm!uO?f0K{HI1UVBGsT)>k(AXB%WbGg)lKi%BH2bumlSL`p1QsZ=4PurSMDdm!y7vMZFQeS9{qQ1*5bNZ7=+&(_U<3P-e zwVJEbHz-%^Tw?mxnWAHnw)C-KZW2TfZR} zbgO6@vYU*U1FyFJTtDix^pCo`mrEWy&D}vvsDbE0PT;PJ`N9US zydH2WM1ae_P>eM9xw&|mv`>Q=?rcKfNa~5)M6Sk{i<3BBBlcNz?n&jXRgtd9*`!@sKZ0lm=O36v>42Jjb_o~vM;3UY#L$%fp1{X=<>jybr3e2atbq*6g=^-!+M zne#}VlBNmM-0 zv9aQnb%7S>3aQ&~=O`gZg zZmU_`c#Q18e%Wz&lXhxmGGR6d6E$B5X8PfIkMm3ac*_6QIk69OzsG#_=UIb<$9_Hl z8+rr~z>T$6TP=v;CP9sLGZ6a~Z8h5#eyV4GCGFGt z17ae;EwobFbU&ewv6+d6N+>Wvv{JO#ud9fiV6_qaZM}$dKh5$>maSJ2RxaPu-PRUO z@7gt8!}I_;*a9+)tOS!&!fr5gqh9KSukS*b26iD~pz|=Gp0Hz(Eo6@VLw#j|=H)#! z+jqd&=(3B8zK#DtsJNV~UhqxbwH3Q>Mm&?8EorYvXf2?wtt`km#f*6rj^IS>KQc~@ z88PJQ1wdYfGlj5w&bj4^km#B+oe$!Eyx-?pSwNP=`~9HgZ@^w7`c@d_Q=K`McY}hl zun}ZFp7~WidL|dBH84OSyMCzU`N9S^sL+MX{0Q;7`#} zd|R}}1)D2?hlmm4ZcT(GrbY{wil_MPmG@!&I<(8JOjh2fHeye`i&Fo*i^!XVQ%gV4 zbv5SmBE>jA%4W!xCEyIR-_2Yo8&$8Hp}(fhTwfeHdy4+>^nr$@=N;tA9vrK8LG9aU zVXny1U)SZv0pB)H*H|PRVm%WrFQ=Eae@&Ufb*+0pQUW0^lQHfJaRGQd!iwQArrOHxz%7&^-JxyyXGP??3?xDK^XtUR$->wb@NHB?ry|FmBZ=&Mq!t zr0gwXkE#t&<~*CJ`G6GZ?Ero_cJJTUH;JSy>`(v(;H`G#QWdyrD_a`9jt__LQbJ4M zzyZ*Fr$+osCS7Ebvx{3Rwj)t1a=kEtU}R8vAxv3TWEfMzYlUx;OhtaA8MkRKL~y9s zGV$YNv6LBq=;;xgmSwdCZP&0jNAqy;m-Y|7zRc;bH)dCz_F=Uv-cHWPLx8S zjnAbm->}O#O~B}P+uX>B)~w9R6@JC8OWk^I)PubU{kwj-$O3q8#-;@fT0^}Isw|qt zCt+SWI#@is&^)R#;@R^0H6f!AX*PdUe_n&U8@cu~kr0S6e7s_q9;Qr@dFqt}FR}Cf zSjgRb>Do&`4pKjsvII|(uA>}-H#w=0C`p+qTqY49J&88$(pz*W;g^C*YgAwq^!D`F zK>3taRNTIPlW;>>nKuk>eE-n>Ws8_Cx3KjgES&({o003o2G{xdQ2DL=v%Mh+F)!Li zJX~H`Z!3x4@GSN786tn+mrHj>kq7t!Smf|k>>=&~>$Rn>!3fo< zmkl(oMJ{5;)zr5ii>rU{)7D&SvGWJHRyPBraQ5Igu*muS^~fkjc_3VOlOt=cJIbOt zbw(nOb81F^7ve&!q`JQ1-Psq4fUulJT6IM96^K)LdMmxdZxD)bI@F zB9;_ff4dNO!D|=(t~E?}gDL_xPz8i~Mv+LQZ&d(%gEu5|LLg?CWc36o4zAgUi;*zm z*~agSH4_&-x4OCtd)1?!ica;c)RCp}+~zE%SKFyNk0pk?0;wCVW~TLn#;x;U9gku0 zW??VlwEpGP)M2IwQ*2$%aLlbply{g=WK0@}*{9MOr^*m@61t}6Rk0>F^EgHgpwmhxR5?xBCQB@Eyqj*gs5ZB~f+hqQv zEU{q0iYgioZQQK6#5-B_XGCN7vgLtnGpYR>n9ybGMu|G05}cVlKG%*dZDK2dA#e|>+uW(akWAN%|u+<## zuO2!li}Dy)THLm@Aw7+9kH1O4B+8}-!8W#_e(S;gIWRFe1*>O<>zy^8CBP|GUQV%E zBaheYJUQ{`Zk`_s1MRAd;w(IL@`EbGUZrmRn)DJj?`=+PoGfOhNU?j1WdPcq?%wPU z&y4Z1B|?Ec%4pY?l$mR!jMG!(@JfCY{ivWUNTO#mh*&!m07}18TjnS6n$uNV>zJ~8 z?JNE&0*$Tnbud8!vgvD=&(095iFr!B_g-~D!I^<>CkD64cYi%pyH^csNYZ6c->#1LHxKu+1dQd5I0Rn#E=O&z)cz^-5;D;>r_4l{{{?3wXZh5DP2U zrRiD`!&nYC2;8v>*f+wI5z#ET!lJ4II4dS*(!lF zPiF6Iaoxx8yKR~Jud0xQ1qBDa_Y*Kq5(I=$pvr#OP)(?Uz2?HZcW3#tl~FxVQqM(g71dXv+I#Ua2Ti<0i)`ER$D1Sw0jPPOd#s~%P z*z9J}2WiEc=nX)O$qSC7P06Ir`Zely?X%W@_L&Wo|2#;6L?&+3Xw~W5tf@=r3b)o; zmxVL+YpVsfDp?OlryfLVByxFYRc_NC8ijHZE*g#Wq$|@!;4c{VYMpj5&TQ+VIc->V z$OKK}X@JT#QsW-x!y4B@GCNmNh(JnUD73yqi)?f3Rh(zC>0Az~;gt%eQc3@;E7T87 z%cOLOSuo#1_lX)X!Hjw8b)8q5!HaLN@7sI#--*CE*b zBT+9CsLv0q%&&!nfR3`G&mL|UyEDjz0G!lJcIg*8Nu=a0M9CrzY-4`h^`KY4!Xr!4x!c+f-1_B)y{3!%4VY@#Kf61avZ zN_NzX>zf>eXTY#Gi}s|8{Ae-pt1H?6V-Mh51Go2|=>8gFfw(zxsdF9wYQ{-JtOR&A zd}ce4?)DpbZa0^RrP!#<0f~?R%5k-cKV1Y1+?aQYd2X%2K_gmJ3)nJoL9i_}3Y;RC zZ*zqdFx&^g=c5A!c+>S!ykAImj^Uf6Y9E{r=t}Jl-Wy^CkK4QYr_A!7WwlpiZKSyn z_jce-gfb)1ggb#v_+u&4yOEU~)yiloCcM@H>=)pp#d<>*&$PNi{8CQ7N`X*kbjn~4 zI&1)u5}XLJ%WliL;IVY7gz^+XvqE%A+lE%0QS?ztomiYGg%l7w?f#F*H!MR-iaaZY z59X?(Oe5D%q+(X-G3w2YcB37G{1i*OOjag%P>H%vKBr^qHXGgsWet?y+5B_b20Ftl zqasFuLvvWwd)zVoRPL_J&+a)*;MgPAPreVHP&Ia>iFa?|>WM3`rHGE&!_!+$w~EU7 z(yE6XMQ@hB`9R)9wYp`pu{(?c9?c)t(VBPVrd}6m-Efa&0AuCO2})DxTZ1GY8`B>w ze-qx#U|3rndQkS~cUqzX*?P7MDA75gHGHEz_17U_W;7Gg6`rpf_D~Qxn|b}3QOnU? z5d2^tmN$-RtDD~}JNQGEuE5S#*S6Zr-yqSD-;_<6eISzEt+;ZL>0+PRzGZX5YHR2W z>(@m@SNR`!sH9cNlnq%tb1ih%|EQc?lvMhtt_q#l!>)2J;GvF)`S|v^YgY%JCvIH% zg$QE6xKMmKoCwf>LK0PJyfNs43GaFK+)3u@q@6#I9I4DF`0c#<35pXXm0z>7_02B*pd>gPr8Kz_I|pOG-mE zP*M%JxSgc%qz5%i&GFZXVSa>zl}wr}492B2161l$<&PY>L$76ml^RSsV7!eagd?5c zJLDFAZ`)8#NP<~dR-%mDrNbwYO%DRZ)%TgM&8BOrvj@$hQa(u?oI_DYmQC+0GeVbl z%V1gBmp57LP=gW-hnhm9U&J0wELv{J|Gk?eKh3o)RphD~Y7JMtNAZD-xpZ{}x;>D~ zg{|V@pFRPX_b7Wjv>$j+Tj{15ujByZMtfDjw@my)wg|ZLBZf;ocQgbEz~y}ZqT82$ zq%5M|00pJuo35#$KDo}}wjsNdIfV`<2g*vKJc5MYuL09;+GD0D-0NRw2 z!2iNJ#>>}NoTC2z`6mh@A$po#v2dTkoJDIHAt`ndQIEocOLiWsP?)O@x)4`~~i)cd)<-r1xwP}&&EJ9&< zG8hHT8mi-pq|wTQvF~(LgpTgz-7+hA<&nKU5v5Ubn|zL!o$)U_l7s9Z545izYUl<> zsZ*nTN=@=eB@Yduh>yMYK(ZPIC!kpG!RJxr9 zs4skB?;$36oZz!l$fbm0w0D680ozNo1(Tp}?CC%@84J}67gX;Op+>3i%J(Gp;l#Dc zJ33f#9%@!d<$PgoV*oRsXb4EW!@f%mtQl5xW9%l_ENa#)6rmHp3P+hRvHa7B9jev_IwnaCo942OfXEt%b+# zm{&!$a8NgHhb=jYQ4ur?g%e-)MzDh)3_J4f?K|VnYO*RRcLpFl*D?2iv2m_=SdgAR znq~FR)2xaqO|JObLz`aUaZp_eyrsg`&NCW0;EEM$=RR5|KV{bkk)AO~;As=}Un%wd z8;pRBoFN)C5wu#D2RBcyU--#dt8rm}tt!wRMjReauI6~nj+|4QA3Tw{`$+KJLVTIqPYv5uPjQ zVSqFN)Re=NPRs{!F>&GMXUs5)n*WR)cBn%}LfRmnd4!;Kkb5UG#hJhgNYS1KpG~2E z68sa#Q6!hP)0-?lEUKe;RYzPnafBoDxDS)TK;D75w%*zJG}6^aO1M8;rN=*m^g?I?PxS+Iw6 z;yCI()_ynk5ftsbB@{BmW>vfm5%YA7c;M&!nrtA z?2R;_bvz|GjqX|qx>0bu)DXn^!Ks-!_!#4`q!?4LGr0FOD%+m0X;nQ=}ni2QN=QT>1{*#xQuPEn(~%eC<5y%F=7Ns9;S&=NLOu<|1HQ4%;cr&xc4o zq0Sq86Mn&XjH#6nk?hr$-aXLqCd%%m`SK&V-soXGjc44EyL`3*)JiqJ zg&d47eSjK+sMcsQ%6fL4TYKTS04} zzuQhN4;iiwg{|uHjIFXCwic#8wT2T}Q-H{|gU^5ZB!RApG7=MG4LsR(WYu*8W`G~sinzDFO+U(&mlVxKb#$eQ1N|7T`W_7a!lY+&%eiO8=Xoa%3Ua5@l09_~0H39Ur>3M33IJ|$Yx zrripPiZw(SYoMBr5u%d36JbcB*&qX>IfVZp2VHL2jP6V~&{U(JuI+^!I$fDqD?kX< zt#*y27Z;n}CvzB{f0~0wR#v`1Z;x8nte6t8`)2Bq->}pilG{sn*!mUOqIT=a!y0c{ zshT|h%qr@=J%M=H*8_jkcCnyF?K{y-cS7-QDJF%Q|G&Avkul5f)&zZ}5e; z3lcbA%>Wxlnp&)qZ;3mHJMZum{ytdeg^7h5XR>*8xf`m78UnIqX@OV829R3(KD7=VVJ8Wd61wp}n3GK4!Z(ozD8d46&!=^hm^4-Uicw$1o24tdx@Bzuj z$1_xNzJ>VNGSpFY33BJ0YzpEv3B)tso_$=|I^Rl(ffjvtv$YPCN`$nG4dtYm3gJ;` z^q8Z__1}IYtUd$-z4bVzNb?8RR(`cQ{^Ex1d4_|dizIPLk*i1O!(n0S($-C!FYezN zmy&~p|MTWpo(t|s3FEt@O}b*GA}n72vm*=ga~mCW= zU5h*pbT_gszdmwEoMIUk@*|ul{01Y>FC@Ks^`CnnrGxa96i|@ja-=y`;1i($wX@!|BR5IWH&U6 z5B-JX1^>4NIu^md$O=YWDt|#f1jzD(0pGPOW8Pex13q&oMQOl=X4k)2FBEsJ`MI1V&g4^WWBw-NEbh>m!xQS zGRy?tOOiPw*xB(&-yLk^6hL}}`1QF$~ad6;>%!sOif8{u3IOy zwX3$(51Q9;e4#Dp3jHmqdkz&QH+z$A9*=03h+}we<3P|~bLa&8OR$%e@c%tD@fXMQ zW4OIqINur-L8$BG_Y$(Y*#wO$p_3#O4M*0(fLev>Er;y$=W#AdT61;bhw2Q=>+xwr z+$IJh+;kic8NDz(e`wD6WBkH1WDR1n_%$ifv_btf;JewJ62dy%mJu1uO&~K475B-F zn-Ks8+0ICQ3WXIlSQPgCk-*f%Ia$qats5mq^u9hdw!6e+7b#tEjcXpY1uX)+VjmQj z5(p(D50SlKCmWFx85uE{uTlY+Nzs&DDb5hAuj1cjL;MCUp24B$e7S(wK-moSLC%cl z?`OA6+9Ww+6_O1>$j_`5qnm}Il!gp2Lw|RNt!9&Gfwp5%jugfhW1D!~c)hSx=krTR zGL#BDpG40mom>Ig13-8rT>r!RES%1a&-@vFjiU$YhIm0E_^fB32pqDd15wuu zkKL}QIRH$OfGYA^TAtDCB|YCD|2ZnbCXcFB<#Aalu#lkNL>=?co|4!PWGB%L^Lz`P z@F_VK%C=cxL>*>3kb&{bR`SM>4~t5m`vQTc38IKw?RZlQ{{QlzMWB@PXp^JLkg7ujXU=Vc_4 z{MwD{q^I2hp+L%AGWY)!JhV@wPE;bUDI4Efh~oeehLiIo2sgFqnc|&!x>~0P*}u4r z4=LnEIJe-UB%1yRQL;L4OJlUY`0kW5WX1v$|A-Kw&{xBpOfMH;;Q{nTs0(xUpXg{3 z!IQhVA;I3jXL+3Wq0OI6rK%PCbgY^!skY?*BoP3@3OTuI+=}9gAX@!!+CCd}PNW2Q zc|En^k*5>b5=t}&-1eEh%Q$a z)e#M0@vkCXmv$dmyL^x1P~@52vzlhxIPB4eT96!iWy}Ejnde?P zob;YK_~iVY1DD@Lw@F?;ue!3>f>BVP3;upKAz0DD^|oz4^|#ahs2RV9)h+0 za1SzjSrS5?I)Ajno5LBGSXkmgF259nJgE~xcd&D5mdFB0={qqTH$w&=+)7*2%D0l* zK57|W7=?F+>mmYJt6>zc()sjF)QnRT>F;Y~n%!=wMVdOuKuUJq{!ZLC)$DfIxI;x{z5Q%O;I0dq(>SK zipy*2!lBrGGLql>l0f`_BM>%*{ekYlnxm_ND#5Rs3vnXhm^bW1BNS8jK>YVp5?(~C zCe`^FJmgUlHEl4EHmh;;?P>LyX6Zavgb7G7H>QJzC9OkYodD>^QnxRsO2xL$q(cD7m2V&;gtC&LRRSrsy>ANb z)kbHzU<0i3`W3n?qRoSGm^KAS5xOyh2m0iW&MPSqbdD(!tM~2qHf_v1kN)5J=(nAs z69m7?OVJYyl-w)Wt`;K4wxdJ$##J^2>~1CPtk7kYL~&AoB0Au~vRxp(4mvA{FC>F$ ziL&YhSzU22QUVYK5ifOyx;;bfA4kyg68J#U*7=s9O782 z+$-vX;dU^=Ate-difH+WQw<2*e(L(yvROL#%t7&j)VZw0B} zm>bX}5rdw$?)%@0|EnAZ@#kbPQ@3t+7JF>sBA!SiiP9ZFtl%e^5Ob z+fz>FD`KJo%<`Xf3y_YRe;iBv+6m0G=I~irX=4xTiC>orZby{&8Jk+hn9Jl|Og5QR zF>VFprz|~en`Gi#5$lF%QM!C-qn={}S!7xCeph0$<^Nn2lYl=(nLC?bFMYT0RLKq3 z>{lg)QPZ!AJ*A_fG7lOWNW~dEggUGrTfg-m&cf#SUq_DPU@>jCbY~T zgIX+oDJx`mq{Le;rg-1p6f}VKpEH(Z701r*NR6(@^oKK|_(z^*oUNQSE29N{InV^d zVWklZIr`W4q$T2(2YeZgUpqjW$$)6+qw*&6fCbD)Yhk|5PpTh=Nz%=VCl9dNz+R|a zTOlP2Lw#reoI!)TL5K(ulT4$bJ zBz@DQ1V6u~Y+cz_OU#jG=RcHz@bAqPD;}il({rUj%E;lX%DEXaVR1f10`8;EMU<@A zCze*1yrpep3=~g^`=n_&Mjvx^#bWFb7^b-guUgu0NO=gWTI6wS7sZv zmMpsMH;GEOp+0baLT3P^9iMYU4fOUo6;J7M6uT$P6$m}>C@^-E?nZe@Ie)~D*}wje z?DoBVf}3#@h@X7MHwFI#j7BPe6YSbxf0`;PE+&1HaA<0wL(1J03+lZ@=fb|I8Hvix zn~4hx=Ncges-6CV;zUZVNlsHO@rT0$wcEw8WmG^lIRPaDVM}Nd>?gq~q&A8))d|yW z(`!K|iYq3ShT^}AGIvJk#jNWpO5f{k#seT6OZ5^eTS$^_&Fjj=4fzd7kPmtY@Uca62PhT3LCUlq)uo`vi&4e)#03)ill6yHQl`2|Iq ze(&FyyRs9Vb99J;AxQc1)z(UQG3-y-tAqZ@SBvv31Wl%6k%Ih8GUwUe$9L4>qd;9l zCBGBcbSXfdfBD0U(y+P^>K5lZIizHfL=&m9f(abCJmR)G5@qm&GOEf+m&lg<4#5CNhd7KH(#oLqni@E%2f`gsEH~Ofoyjq0==r+NI4l+TiEiSkpM26Ibp8Tc>FG7m>Sc$8Az&XLZlJME~rpBmQf8 z^mSt1`@h<|(x@h~EX>SlwQU^is4N14#svkDT^5OGL!eQX!xD(H%BF~{0ojSR8-fs9 z1QifkP!VJi837Rp+CUVLG$M<#X%JCCgKQyWVeShE*gbvDoH_GnW~d*ePEoJwy;pC! z_q+Fg*VMFy`_XE!rw4|M}4x5JrdZ#d4sXpev}RM}nJ@URH;$T_4G%*ozi%oq}Xjzt_{QUV>&-E&Avx z%K_2|Hl1AQ(E(L7In|>8KxA%7`X}bo^Z8i&CxQSn-~fxf!UPO2p^wL5K_olCDG6JX zWp2Z~)=el40|_HJGLcMeZ#mTL&*##oB3e+aU;p0&j_Rx{kC3H~<@pkL4s4=QU{t^-FnS!BpVW=fO;$^IS_^7r%yi5x-bTCB|_z%`6?)9u7TA zrKha-U@bM*dMd^S7oX}}myofz-=@rUtS)4$r1e>t*u8N4xk(b7oC_uW5vq_@*_MfAIKUED-Jil4S6!tWXa{+ zs6pe`V0}lR&@c!2Huu}t1MY-YSpYw*JDFh3F@nzpnhsZDlz{_bOL68CI9C#I%C-!J+YEoh|r6eWg&GxUUXf$9QhIOHj zj$(xU=m z5P#T3tOpaL>k@8dy=)ID1-V@Y!t!H?f47#e!Ld5k+_OjpKsQld3C>l>oiMR27+mL2 z_7W5qs#Oy@cn&;L=r8pFBZ;a2%>jWFr4?$>AoHDUx0+lO*C=19c2sE$LLeA5u{50c z4a+T+;60thB7xQe9C{TD&A7--VESDNyO)gkSkj&wF*sQa5D2Ni*EsCynH@rf&fc83 z$3i~w=0*{4)CXJ?C}HznKoh|pN`uX{E*%HfEK#{rNO-c{UO1Qk`BDYM+S{hOpnzWR z^oa*&JldmE!kMtCV$(Vy7-gd#K^xl`n)4n7$8TLyPaqIbF!QaPoEVT0kxNFj+U~bI z7Tp0Gr08xhGys?c;PXEz_ZYxB6uw`dCCP%_7)FRSflvbyQ)W(%Jk+p`^t`Q&)@-ha z>&uciGSCGAQ&A>pT&@5apb&~%+1Y1d44_xQ&;#fpf8rCgB?C$$1gMYH1@e_BCjj<- zG@PJf76gJQ(!fBkLEd8EGqr`Cg6id^T2_f#hlUg^1ndVeD-)tbPyiN&>XCL2P0!v> z^)JF9ihC1yf)n{>P!TX%-DKz-WIjAAAoPqGiULHh7t1eX9Rc4@WdF#|-v(ltqyN>y z*O0Nm_plNW=USKdfNqH54xz;$x^Xkw$XLkDV&G*1o>3V2;GiJUVWts$h*w>E=MVdW z;^OTHEdhU=oQZNUL`x4DrY`k>X{KOiQ*jZNF{MdKRu7pB!nQTHw(!HiKqi$+wm+{u z?udinonAAEw0DkZAb}1!6<>rxm>_gdfzc9u>sy!gjVEuy1ZWo91)X3BpdtSJYK*AA zCs>Dchaa!U_-|6jb%0IwW<)-G8?g4%OT%$CFa%);QFi#VT$nX4yaI}$PmyEMhe^8L z6c>;4YH4XfoW_79NtbvWWL}L{vls(c=uNusg9Dex$E6$fYjbw8#>dShx+&d7MQzzo zT9xHqX9CL81*-gwx{iykWZp`F_C?3wDPRnMPrPg}T}4?C3jbX_-hRZQFi z`Y$m`Z<4ww9z-D_Tv>aqKY0X<1TlzE1vXP@ zNFFdiD-A4jvC&%0sItoky$6$FRU!%85#iPL z{D>zPHX;p#)HrEIWetv{H}Iq#&LFlW#)eU#SVFmCZf-3_lpA2@uJMf;Ng>{G-VOT} za;eEED+{ya$h!p4=`Xy*Fo-tnvZk*in@LR&?vfIpTxL2R3%NIRlTP6P3;ju^!zA{B-8H)VQNYV2!z?R;b!+oU5yCD~kK_&zWCk zwX-u>kH7rm*s6JJmf+%f>`dbEFSiI;R?k^^1Q&Ph32K_v7KMaDp{D@@CX6C!P^e0D z&)nn_Qfmi7C|5A};weZF7P7yVFCT_Z<{i!+D`qkz&R*Q&;y!yWI9ZFI_fWx=>(T#_ zro<4ZQ}xZh?nlf*Wl(4ATIiYSG4aRYztA)PwLPQe%^x~b5-THEjHX)we~)=?u<>{) zt-W793?V*q$2R>XQIka(MnZgp+=h#G<9sQ|(ZcerAdGIFy9v(hPxDIxw}N~)Fc2fN zC_lu-{bpx)e#LeyR)qJ#uPO7ZoBgh=*6~-3msW&)vd^TZ?!NPX6(eWkQK$IeeZ!MF zoL;A_FO^5j5X2>ufxdBlz7^uy8k$Ifl!wYVG@diN)jNT&?NKDb`;S z#ff|mjd`-f=YfV;L%OW5$loCGGk?#yR2{I+KJC`-&KL=8=l%2}_(pRCaPGDU5r^+hSz|be~@yb8Pjy$4!=`hJwVKqHHAeACF){+a%(-VczpH*S@wTL_ zH!YkQt9jt61bTJy``jK_-6)B{>d?70Q zmQFFx=cJVKBbl-8nq9=v%U_-=_$?P+#{U#2Au-`C4UBmcOukdiLvhEgQ$?aB-O}c| zm5MnBpUQ`sI4^@8*rze|c4qHua=dxhz3pkdCUeGnW`t(9v}8=y(W9ccxpbW9$;<%n z;M^E=XR`19{iBsr<4LL5}a zO%I>W(l<&j{Bo8UPQ*{d#eLg-=y=qTTMr-J95$U)c(&xWM+Cfp&UE&QkWhok!oDl$ zjs+oLdQ>b30W-5>K?s=ch<`;pSP+k<`)Xm#ER30%I%eLooq+7)P5qe|JvpEYdYv1DUb>7&7tgf{_`IV zE~_qj9JVm<%RBSA8AexBs_VpWg|+6EauU*`qoa8hk$B2H;0MZ9tqLEO+5ujyz2ct~ zp*s4xx=YEtrsY7aZnfjVw2Y4+$GPL!mS?QyblT6EHvmWMH862x4?jy1PI zi@A2Dq~>w96`u07CRT<2Fx9vB^Q$By=$GSh=@bxcV`MyQzgIASMm^HK3WEV%z-2_A zQvKW6gx?q5^{hKhmL|R(CxO8z_*7_r;=kz;!#z7^83e9Z&G_Xi&E?yN!nnUiru06& zc8#b^jyl49RPp(BqU^L5qdr%Cbk^AWv1CeOiuC3XQXE7ohg=L&mnNo%M5I_H${FXT zss}RMkQT#$hYcS@lD#{Vs?tpYch_6l>+OcE#rMo}{WUTxsOBtsW#0xa9)YW@) zb@7f;Avh7WV7L zipnb1${ITA>N*-K3d+hl%F0cpIx=4s@b-1{^a%a)0>AYAlmP`c&%Pnh)7zaCc);85 W&&#leG9%H)Fvgo1 diff --git a/docs/diagrams/Bluewave Uptime Monitor Flow.pdf b/docs/diagrams/Bluewave Uptime Monitor Flow.pdf deleted file mode 100644 index 97d56d781c1e07373cbef24cf6ef681806f5efb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20107 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 T_x`%}>(^^P*4LM%3@IQb4boE5-6c67ox?E1 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 z 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

h(;ASiuM22!-7i$$Gi-VPxtNnQ&)I!UMqldpdB zu}>eA_ry)L+wTMd28yW3)$cJ@PWs}q`n3vfu1|t*7n$F=*4HgAvawNZC6D5RQ!_(F zvdlv*OI3l579yuh`_H6<#s=25o-pK*T6!~2gPUzK(nfUyr_buC9rJv+RjgZKCe$1= zx9{W^V*#8ou8EEp>v<#!W#d_DQRc83DOV)uGMqt+d+Rp!fb8FViZeL&Oz95MVB zS455K8eS({RP&ktbe!!^IN;IRE$TO#3$y04pLe|@?iEBJsG6%rEH#iXn)op%D%Q8H zRv*?$)I9>YJ_;A^AXls~F(aF5tuk~hzN2S|k{FvUE|^;FH}9Hr!LLE7eeU0WJw zc9Fmx`!q*hKXSGu+D#hR7;lS~WY1~A>%!dG^-QRWxvI))4kKlu7de^U zrwhlJ4CjhZkpI|Gc|ogX%zDg0kG#0*>8spBcKu5e(7k#4DK{pwF%eM%aTyro_qxr0 z%SeGjarP7|ZvCz*T;F+S%WX(oHEOt~DQtDj`NoN^QOngJDvEe|xIMeBNG_N5GLR|e z!ej)0+BQ1&ph=p?mP}C3-TD?hB<8G&@^NC}?pB(jw!V-Comrd1_#;#l+Qi8_uk)Ow zKO#6&cPg?#*@dybh_b}M@u=cbXWabkmXNvk$SEG~z#EyKdhC@n5byemx}^I4--PKn zG77wb#p{E%!r%73TyL<`uM~XKMR3AWo&6$5O$;nq3wb?%anZ$*|C%jgt#$UKoK*vQxh8@>Lmp5o=ROuO&S?HN zYIrbJ+~+Z9h)gv+VXCD4ZTiN@VFeb{@rJl4G2lEhvhA&hQKC5TIyurxk~4a@b^WHP z`JgVc_6V-n2G{yjz~GR!!P;zFol{n>wL7tM^c-t7A~P+^$#IC=LKq{{yn8x%cz0mG#U9(OcEf~0UKg;sq%bhU5qEABg7hNROp?(gR=@pt4^92Zp@pEls*P&hFu_RA}4tg!O8Oq=itlA!WdDs6Olu z1=PsURJ6l3a+g?^ts|E1YiN`w9=tL25~@wHQ0lqIKe*@;eAUqxD}mo+z|0fIwwmI|lP zFv0gM-_B04DyXSaAi2|gPjn~u-XRBR1Ke}=Sk@F1+i0x+*_?ILxm#3r>f0-4yt1L@ zgMp0ft1mB=m-D7b1`rnX=nM+MKThcxlqAx#vNI&TAD!bIs$L(zNEw&T83$U`<9)xA)}*bS!ZnwkaHU;`^PkW|ci8 z>r_{msO4vhWzSd*V&JrKvcFa2O8gKs2lgT5oN;XTaXIIc&9iqsAC>vVq<}u8g3va- zoBOjav$k(&DS+5R4VdssC=h5Cs^+lEgUh|I7KR@k$hoBjDeY`|lKH$|*3-Ys9J8Y& zt$C#n)cp92EX3ugr+8P7Oy6h*i#n16-LhI(kukt^`86rU5ZOjTQ(KjblGzpSue}kp z>}r$BS0-FhHd9sRaUegbcAfV*l&`F<*l`@ayGU3|Nd*xRuc&HTr+5cSCeKt7O;u^Y zJC#-EekswTa(fRsTR{N_b*SmuBQ$j$EsT@(?CKm?4=kXzQ6cIC;~Ffdj2D9NZX)$+I!~kw9IefMggy`0ShwUB#)p`Ity*%Ts$eED7rPMWyG3u30~e*8dNX4Ud6a)0P3EKN4wYca{nmMt{+c^LRBRL zM3M|0z;DY7!6^Xa#<7>~V*gxa(L1;VARgI_Z^c6i;LZBr!3S~$cFjwlZkGBruUD

6=!a2_m{Pre}i_$Dl?Z`JM8t5w|S% zeKwK#MdGpgo0s9Um$tV@<N$t zw7l<(h#SC5q1f-Gz^yRU%qr(rsMB*>|D;py5ceA3yL$7G07E&In^-kcpVJR; zYMcm;*uIfMl!fTrQZv^gb#<+~RgDAnL5Gv$KLLub*KkzpIlL`w5Y>#=$GHVe1@}4J zBtH9MQ%mn9shFuo;=~4%bCQgaWzf1~&bZI1gKKDCc}Y}Ibi?Yq%8cBQ;Zhse=e|^+ zVpexZHon^jC<*$TgYiRt(eVKoCMSw)7?QYs`Su6}08Zuhe%9hR)p{x@qVFx!aNae=SZ`(&5A^i$NY+}eOq+M< zxs>04P{n(Q&0M#`zjxCrfWv^dQS4bLQ4xw*QMZAuV=VYfKFYvYufsoLQY9&Q%TCD- zh#0@9((FRC-U`g^wv+ElHqd)1*e|#DN0nRLID3c;ltG;NL#{T=nKX<$I1R>~qZM?x zklk)q1d*St-zAaERl`X*88L)NUR7|H!S=EuLbVRQ+NffMWc5v@#K@6nFSh6VOVZd^ z3S)NM8!81i$;7OPMcMQ{9c*Y?P9YTkmPf8F_L1j>vPw@cpjs*;EX&ck%Y@s2(LlSY z09cMFW-(BeRxW)nx6ETJX{R^4_@Z3~R(o)cV2R1PI$Qvlg8|a*G(egsPqTbL)yf94 zYBvq#KBHmx_JhFXko?K-h#khd*A1^n8sA&%AH7b1|AsZ^bo?Lj{~P@L`=NXFwCFb; zPBKFu9(oAFj~Z8_)!EBHem}fkPI7sArP49054NWMP)VV*BRVy+d7&5I2-yl~ARo6g zQ`IHB!qx5&k}wlezGJmVQZG%C{ta#d59%4xO%A{V>J8-grB@-Th^>h6DDV?7lUA7) zr?%L%?%<*MG+9ga3S%V`WooA=dF0gTubrn->LPj?!D(Q6du5T0WyAHz_Fb4mzI9_E zK*j!26u*t%Qo5jm9V8$ik~Ad7eaVltQnB7)rBOxwoHP3FA$qxq@MyOO#fCLk70g0x zB7AgQV$G($L6G$)opHandu*nNyQ^i%^5}y%h|Ty2lBE9q`Fz8B8N!Jc%3qAXiPzme z5koFB+RUIgyL)eMv=c|2a$!gfd`sQc6j;E(i}l#dI*6mUT8ubA2~YjgcjQQ9>oo{8 zb#cpxur^&Izm}>Jy-;|KdxHeYFpGNDjcgUm1jH%L2XmI~oQ)2w1d@^;Tiuav@=C=3 zr>SM1;j;EE1a~7Wh6Pl&Jrdk9YsrV@R7~Mxq4LDBaYpb!Z&Ver*Yo`oULdzNqjq;J z_AjiB69UWzT%aI2#b>G#z$@rkuRUrew|ND8zV#{xGq8B_ir;Rk)e%=te;Qq4!)ZRd z!q#csi#?3e&1db4JCLAg*v}^gY;r-7KtmS%0Ei7Pc1N>fb4 zK)r``kFX@Ob$!I}V8-$jUT+tgpP%_i%fjWsoJ2I;XW~uEM8P7jEjg^+Bp{z0p0p>lZ@AvklV08EZUDXO`%q?YIAj|l( z=1kFPh(AUHX)|hbW+|t=+_Ow#yFaYHSJ2;7DIHAKHy-2!Eijv%zjf_s^jN!}yJIrc zW4psjs6hO4z&HPUmiXt!l~#4{`v%vM(9u*E+KayF3<9#@Xmg0oTuT{$iFkG7#qAZ8 zq@CnqN;{o3J+h;FWDO0y>LK4;f4Yv5Ppc=55bdz~PYBTVQjk{B0dtXb>eOP($b0Jd$dYY=Vl9lepB9@N>5&1F zS21*xjwjJ@ha(sn!e8<^(+91V5>-H%&BOFkglQ=&=pqxCg z(e3(3LN}}!%UDI=fqlNz9S6*=$BAp_?j&w|Yp!`QMmlCa6{2jbWN>rA7ODU1=oGXUweZ8li!j=A_ofpU5fn1kLN|%*&Q?)WPoD2EY|ot0 zYT34q9Q`ny&g>xoF-^o|CNwnKk&`sOIbCN5clSikJ(>+r(k_vPEG(KsCNeiy{2tY% zCQB&M)8k!LMq#HX7Y&`v6QU>9GQPW@d?#*&a?#LS8@&#EHFk3R;Nz1y1V|2mfpBPl zR|&wZh}tE~E3*SO@rjqRw3m3HqdBZM@Ai#`1fE>zi<}-KuU*piQuK6q5T!yIFOO0K zH9DCSWMJJ}Eg4#`hn=29ZUTX5Zu2Rf|AR^LuSE5!M4XGDb(bgWi(j;AGpmIV*_{p)^_&BypseM( z?lIT33oFOva-MDy$B`;6?%q0q^Cpfur_wa_SC8P0-~l-)lR!gImL&@p=^oPbl?+(A zE25qFp(k-gO55MP3|HlB+>ItgGv#`X=^r;arGxPWpZ$K3v-g5G6g5}bjJu3VRh{HM2Qwg-N!44P@Wo@spB;Bhc5W=0AekAqS_GIEVx0r!of{}7-DmdEp3>gxi#SJ~ zo+LMz8v`LLS^r8g&~q^AzYN2iGrWVePO7~f>l}_vpv2?j)Hcyw`;W8ha zYhM)kr<*!m{1(W0G{aD%Q*-|r5hJ;O=?hmnkTp{&cnn-NhUSe`0gVXzXiv{C+iA-( znfh$#T1j?k$8xfor=xaV=M|tMk&TEI6&JS%Ls5K(nn^J9`upM*DPL5yXC&DT^!#?f z_HN@%_B1?3GiF)_X=>L@?!wDr#!pqXY_u1n{9%#fKn1tLpYay|JX62)-K*%Bt#32y zETI^Mn3*0ZkQST*QJ$#hPOvn!tf-c{yG zy6E*SZRI0=d03k(Wc?sH^uNRNiq<4IicUOl6h;LW6Fd``Pe zn4`Mh`6v~+!+B5YChgr#A{dibGD(@$w>~~WFuVMS7b=-seHXEw0O|$*i;zpUWn;&n znvCH}W_3#wBVKo~D5?T*u>(~Ahy=)UnFe&XSn}KzR(*I=e~e$fbbAz-J(*c-1#66` z^o%#2ym^_{nZHr`uzNrja$`vs0(tBbI+d&@aBSGBVC4!>2_wCV3IrZ`Z{ zc{LtdG_q*?tMgU8XNv%3P1@)sPAA{7sz86-d@^dmLN&YY>a*Gd4tBsRLQ^Zk|9Feb zhcDNX-fcu}YE)r;^ln^;meIMZcB$ewPrH~RLkAF$wBF*E+IV(m6;t&hF8Hn+KyD&k zBIBv>YL{8oZ7gN(ZiTJ8oI~9$Ddx4F8MXT}OEzU4dh3HPp(9O$v(hFWd0*eU@N;p= zb(x+&W6QV>@x(t}dQu(z{7p;B-9inommzG~cwYQR>BrOWrAvE;dk_U!KW$t0t2ZZK z*11@r^PN1O@ZPDS^v#aVTPWl!1HGJl3GbJLg+YZKE6?9wJCS4ksN9);ZwQna1zVgN z@O`~Fq)IIiRQRG;{C7sr|AoElAK_sFFtm}@UGFKr+zQUYOYMRR&U(%q&}N(f2Z{qs z@4BDg`S9W#_2pY*0Mr&ae|G7g$3I*v00iHsbOr;C1)y9ezYpFe*IdY6gcPYCHE?a? zV0%6#r1}9vv#KwR=<~U6KPK9-q7VFQXx2J4?(L{vhoh%X{cD*0$Dkf?2>i4Y$Xdpe_xk`WHu|AOuLuX?-f|}BJEnpi%K;c? zBBl?3ah3p-Mn8bD$n0zmFt#@wDR;e5zI1$X19*DeJ=zg>-iV=RhPoiCaqqPJ6)_H& z^IG)hoj*pA8U|(-i@!(CCm?@2_UDknzm5$(1-V*T9MBm~X>kG>kj0^>G z*yNwgX2<<L?Ji)=RW<>b?qB%CO3<3R=j*Ab|2%vGU~_>T6J<8R=YYT7 z%G_|4w;HdBm~zrN!^3wl7eGGV4eQN0`M>1mf9Bc%CPpCfAIS`}w5s@ylTP^`cqssq zW(d?UCbWT&86ctzm$Bw zVfo{0%Q>>!PdDlm+GGh5|>K4-ih0G-Dm&lGxW_lX$Bb~n{)b5nyaGYY|B(C-?Z#i@nr`;K%t6reZ}|UKn#AFZIRReg`!U}(x%7=P^e6iM zZ^!;rhNqzK#}aOS5(2KgTZvGtcw&EE73{*Cb^FNU5AD(EC;{cnuHnR-*~GC9?!y~H zTF(0~bLN>Bl3KMe;EJti^$z^J08ZPt@?)kg1YyMVf&la19jCP}{A0Yp%|qeLS2G%m zngfkwxE0Xiz-nJYyIbS;mwnhTb)uMuvb_%yq(Jx?L)pT4;1ti_sNUD|1z7yxc?D^3;~bnE^J6G+V<{T8-+D?V;}$?IR=s{7d3#xhgOUC>*z#Y9r92@H9=gqNc}NA2 zuRn#q6#Fz|BV+TTI9a5&*ssAo?O-YJ*TAH@J|O=(`Uo45yg@8Af9jvBY(>g|!5I5} zkF`uc!0A!lYf+YQ#^cy;67gJdF2YMRfamQFFw}GO=}*>;asgPj=*E}LL14geuBj3l zt*02TvWI45nuDey0cP+x{I7y4X81dH5?YfV)<3g=|CN8^(=B|(=38Xf|4RiRlAZDX zYeZJni?$lCi{LwVTi1U{78kXo?-93*Z?7vOj8r^haidTF5#v$=%*yo4oUH9X58NMX z|7qISw&1C1&EHTWAbFUoo}SU@miWX(MYE)*1aV{IEyU`(hWLHlmN)hWeAaO-aTV|% zdmgQ|o#Xh2N3VVgMM(Nid~))zj`?>z(m@-Rodk2}AdJp=-sDtaj2GYE*Vq*uy)xFU zMI48pP<39%~R{BzbXAc zru#2B`}50FIL~)fcG!xFmBqnyT3F2D>?7$YAS?{=zET9R{WXpy?Se<0&}V3jH*8Dp z0pg0d(ci);sl&xUMZ;YaTSXuN9Z15*ssDZEzw$b%emW1p@uyf-A}+t5C{$2relX&* z6$x6EOT4znauoi}BHb9 zSq;b~9jhbT%gprFc4rm>!E0+-OEG62pw;5ni516loC=O2=4z;S-oP zY2l6+!CU?^VjX3nt=Suet<%#lYuwjnIn;TB$hWvwLoVjsZT;5_&HF(?RlRb#pdmn~-s6quj_GPs+9k8U)- zP|>fwo_NM#FT-X76A_m<{96(m?-_4l0NI0T_ww3xragztuztp$TmP}p<^Km{D>vAb zb9`Lq5wGW}EA)rao7J}8*&MJC4U*OifN!NUp$lDFDspV?7m|S0+Z*n5$=jfrSNQL& z6k#Zr-j$0M*~(J^&d}m{T?E6pF8S2^1H7!esAIbS_MVl!TWn%Z;`Qv)d%3bVS?t8| z`I`G@q@3FJitUQ>iXwb*iVtlY$d%^aX=1>$|8XqeQK|1=D-B{}1r`PeO(FU2x4pT4 z^QP70>pkM?DgQDv%xGYg>)=#m<*N50h6YVZR%+z^QUOdpk?sIhvxrM+`@@yEd2Jv0RKAdLNsgIb$gNNr9hQ^*uMPOm=iL9el^`tjEiP;I%d0S>u8G}wu^-1xG8BcU zfVSDMi+L-+;U@QaSt)6;vjgfm8abh^5w9}EA*QZEaX`fW45bMdZ96Iy@sT&VXJb0j zU*C0iW~Jg-!sd$@FVNn8(2~&l4*PhFIWvxB0MCQO#(w{3Jsb`B@w;taf%R3PLvmG; zs`yUi!ca0fY3J|pkM4f_@0LxoxjQ#%9Cd8X@92y4Svb$P#E>NSW}B_6q*z zcb@GX%y`xd4~drVzXN_cml(@B!_I?yIiQ(XkN{U6dW_{rE3jcnF%>tc`w001*bB7t zCg;@$T+FR3zE0T)T;G?+g&~prr{VAtbPwer?S;+le3)q7*$@ABa!_d*n{;KN-mcgM z@ua@I-M9<~ESs;uo99{I3SmxMjyoSky5#NSHgu8=Dt2+g1jNe1U!}782D#G1TIhYw zf7i40=htMDoCY8v2FY<)qpcJJpNt(yg{d zj|RP(0}Tf+AD2C`e|r8vEFgs=#h>KL_qzU`m8VV(iHW$ri)fA7aWmFTD6H+|zgwFB z@J!DE=2PBJd4;Jkovzp+K7s`14EC{5ewO=(@Qj+R_Zo1`XD@OZSPmfzUk^Z)VIQ z0n_p5N&T0#z3(iw|E0N;&#dafIQnz{KVqWNP8r}Xy8GJw_XVR$8MN~H5`U;HA@+e| z@$-KPK%c4NbE_&qHsDXP(uEzXuKlJz+n|?<0Cd+o5W8a## z#r6yA*t_e2cco>sq-;>>?i;0+x}6PRd}sBdx{yaImt2K)Ub*K7d7637I@kkdCEa@8 zBdKq~)DK@ELm(H(jF6Al8FJM3Arx;EecmpqCz|#ie7Ga!$vckj_JbYy)`d9Q*`pH; zTL!fF-!wIW#Q8ZM(A$3L9}nR;{Fcd#+YNIpBbKIH>EMJ0QJ3JL0Ja!&GR^Au^QVM~ z#4p0?n&EQM$31RU&M|gE-I|;z1TtiVkgEG+JbUf;sFdc#5lTevo>x^f$gZi1>aigG zQ)9{vNS9=;dy~(;4pD}<7Dzib!oyuovNA07$zjWx+zP>EX@9x9{~(0_l2ZRr9-jg= z#J~I#_`7Za{%P+wzQ-9ZpY9mQ{1MboPybZTcNCXau0xa`=M%KPS$)#@zsm(euC-4^ zACQZY?@o3<$H}hT1DvczUO6p<7LwDNpd)o1*dDaGkt2_E)e%5AoQG}}Pb7}z{gCar z?%3Vd+P^pC{OfLi=5YN?^TzUWUBTa@Z5@LcrtZ#Gz{0yj+CpE#SX2uBPOo9 z;(8=zqWALpKIZuG;n46A@=ar!8lU~Jg;!%=m*R6M5I6mZic)sJSjy^qw&z?S-yRuj z(G_4Svlp3-go}I*t{!}FtS!|XYz`(rPS}hKn|^mj;)jdl_69s2;E4aO!_)L19{IbX z`#*w6SsPBFzlFwl@-$a!tqOnl$M2%Mk7J)2G);dzJ2O^lQ)%2aaV!4o>!CeW6;?Xr zFO;lBF>}kS+-)v7kulv2$+NNhdJgB*Lzl;=pk6ynAxp-cK#0(Pjd3`Ol)N{Z!l=Yp z0g63VOIz4n>!A@inw)Tu`-kpUC9m)PgX)KZf3$1sz-{-#mmy;R$pTzKB0prmVuVsC z%=z{f?0&A+htssP+ij807a)9T8r!JjNP!3LE9YpvT%GQTxpvfNmbzd6D0B&A1n=iL6zctcassT^k{u9=?E0;2&ym|QaxUT)U%x$h;j#|n%+%D(#1LBfO!tn= z;W=7?oIYD0sCp}2K^yj@^$6ea?$fH{0Vn)K@~*pfAL;40{K*BUdj$@akZ)*B-~rcn zx3y#U*qZ!~WdG9%?zJI$AFt&66>E=0YSuhKeKsZ8Twm~(d#_~W{;GGEk!ObAfxPSf z-C3Eh-7^2#T*gi9JXA19mk5l$z!SwQ==FHJI9*WhLdn!ViFF5XP_C)w@1CZK2*7Ml zEpHQOwBH5!1HK2{U;ZuVuCqGPb#I{0^PRJXlv?_JpqY8k3InV;(>hndpq)$jO46mD z?c=vdK0*J8=BVXINBe)_*0dG<1T3LGN7q4bGpuyYc0S^BuAnz^_`L$l2Oj1zPloI=Jfwg_@( zs0)u(2=8svmIv-!LRC~d?m%W_kqx<$@HrmGk=CPe=LBr|n}PSiEVD@$SrkR1w92R& zJIBUTTH|b1*p)70@R-n)3H!yLLHA#AIf-KuICg-G`};?%#ukj@`}M{_jP3iEpQ?`J z69Rf7-6y388|QQam63tnTF3V8dh@=(=W$fhWe^^&Z=PkKL7`zcjE_AgiRITVcUGEsdJm4e{9vb8lW=a@ezC%<%?nmr zhvz1NXcpe&f}BYC6d4MccKIu6_u2BSGQ^ZdT_~3DnYpB@4b$1lE!e0ru?o2UMGPvk zNe}ZBYF;c!3mYUXj(EiW${Y3OQCzs`&%?nu+<` z8`0;F%oOu;XXuV*Zu@e3$(~q_h%@5b>zn>5C(>7+XTAG!wo}*IkPDC1PV1AWJxIi= z+@H&p26w#eE$QV1;rA13y>YyP^TW~IITzJNB8}^Yj~D+fGC&ge&i^g)pG^JxAV*YOB+}6DGLw+jXhwt#A1@Z+EJhEmM_5 zy(LI*F8@ebfkf`{k3??%0MDZnV!;b76)4tDl>~m^{C^CZ@9KdD*t%|+{zNrHg-@Kw z&r3DDHdFkND~|gp35bP(*j|_`)biTNrRwNG&nLTQ90q6SfC#m&ffWe@ABjJ6b(o}% zjCj}4sDVCYhBbaAv5<5&Hg?Bh?;m3Gj_bj@ z=>M@2K+e9V#qlX}Sy^&5Z_Lt)kGvVk19Z+C6Yot!RzoibDMLCweDMZzA8Sso41*=B z3i~so*}4PsGDvkjp>@ygYNyIQ#zg!ucHw8aaWUX%;P-PaTWJ$&-|sJfHp39}oOmv*B}^ zlbTKye^mZRx8XBU4q33=23D@KL4Oc=9JuQb2GY~+o0mdQzIOn}NhGROGYoai;dqdz(V^ch$n>TlM##*^FW zhp#!uDr7Zr9pyd(jXCXE8*_#>d!A65BU7Kmsy;xgaEB8Pai!O%7uoVCOOM z@O2bgj~&h0Vr(!!P=&CNX!cXMADKp=Q16qlE$}VY3_XDa`tik>MGOCWhXkcD6=7g@ zCz6e&Fyotlr|Bd|ITR0h7Em16)MG5w982<=E{d|73c=&?&`qRKOcfgg%&CAq>3+xA z6h$H`rhr*@)XnA;TVmW4m(Sc%ugPBTWIrnuVj}RAKuRQ%{e;QP+{9k0f5C>_WUDeC zkRhw^3ChEO-OOrs$NM$-I)){~tXzTIf$%m`@CcxDgNp?{@|VXfg#lA6+KAs zaCA+A^7+U#bjI0x;76{vB@)Ef5^6=8~u_cAjhPxfNo(Atup;Eqs>k#HTIV)pLg8`!>8tH>g7rHpKcQO5awIypy9wC3Tb%J zO6}eegS((ByQ`mFP+E_SOW!4yV%#pymk8*_STsAAb4T zzsSV!dO89R5};qo_QYUW$VS$@Dx8U6jVxM4cESFjK-s12FYIOb25K9MgrMQC)fn+e)DJ0OR{6!|hxDOH?SwmvbC)s2c zK5%;}1@t8J9?O#{$W*Uz*j{D5g>QSZ!=5J)NO1(xJWFeM`OWaqGtt~NQxMPZ9Ul~= zAv|}&SqWfHq|rxP;tF zc!l!k-Y1r_@Vp^+7Id%mO)8dsD(AS0+f%o}EO@lt^eoE?HCqhB-{(m=cw+8hHzWIb zfTQh(p>A-8)B>?R)+;l5d0??gk112RVFj;=x1>`xB?lnlo9iIA!l@}5yMSHd&F;xu zrA6)pmlZ!A8HT1k*p-|H`LLah7~jZ@$)&Of*=U^rrZ;;Vwf~Wr=1J5#o2I=?0VY7{ z49uA&%HmBw680MEYdHzh!v09{ZEger*!-p82#gc-$4+bp9j^_AXAB34`YRf#9(v%AtabvqRzhXPk zUa&`te0*wGNo<+&$pEO|k`7bO`2!>2!qm53IGJ)w(BOXbg4LUI-b{6^?h%&fn298< zIQe>{F#L|vGal{mdou_Xw zsGFFk>wyEXNx1)mREs+!?^x`PS2Ay3LHZ!DvEQCggXHf0F3EndP1z(L?!DT@7Kv(p zWwiiNg*@`}LC}D8t63O)=E)lHlQjNy)099Vp->-o9`#^FNTksg$)8k@60hZFnJ=7c zKC_2_N<-`qxU`c=VX9ghJYdyJLzdBwJp@Rhpw^LZX#HeT-!*s(+kzFw_i`=vy;v2H-`c$7CwE{e4qHJc#TnUt8_b9Se;Av;M+=k{ikk`^*h>2-HRW7CePqcT11;Ztn-7H9-T zK-J0;A;{c=S7#&VDNye8_~1K%*6HkPWSU3KBh?Rvb=B~ zwzDB`vcHRck

- -Here's how to set up a new maintenance window: - -**General settings:** - -* **Maintenance repeat**: Select how often the maintenance window should repeat (e.g., don't repeat, daily, weekly). -* **Date**: Select the date the maintenance window should start. -* **Start time**: Set the time of day the window begins. -* **Duration**: Specify how long the maintenance window should last (in seconds, minutes, or hours). - -**Monitor related settings:** - -* **Friendly Name**: Provide a descriptive name for the maintenance window (e.g., "Maintenance at 10:37 PM for 30 minutes"). -* **Add Monitors**: Select the specific monitors (URLs) for which this maintenance window will apply by typing and selecting from the list. - -Once all fields are filled, click **Create maintenance** to save the new window. - -### Editing a maintenance window - -* To edit an existing window, click the gear icon under **Actions** in the table view. -* Update the required fields and save your changes. - diff --git a/index.html b/index.html index ee30de68d..2d13bd25d 100644 --- a/index.html +++ b/index.html @@ -1 +1,16 @@ -

Checkmate web page

+ + + + + + + + Checkmate + + + +
+ + + + \ No newline at end of file diff --git a/Client/package-lock.json b/package-lock.json similarity index 100% rename from Client/package-lock.json rename to package-lock.json diff --git a/Client/package.json b/package.json similarity index 100% rename from Client/package.json rename to package.json diff --git a/Client/public/checkmate_favicon.svg b/public/checkmate_favicon.svg similarity index 100% rename from Client/public/checkmate_favicon.svg rename to public/checkmate_favicon.svg diff --git a/Client/src/App.jsx b/src/App.jsx similarity index 100% rename from Client/src/App.jsx rename to src/App.jsx diff --git a/Client/src/Components/ActionsMenu/index.jsx b/src/Components/ActionsMenu/index.jsx similarity index 100% rename from Client/src/Components/ActionsMenu/index.jsx rename to src/Components/ActionsMenu/index.jsx diff --git a/Client/src/Components/Alert/index.css b/src/Components/Alert/index.css similarity index 100% rename from Client/src/Components/Alert/index.css rename to src/Components/Alert/index.css diff --git a/Client/src/Components/Alert/index.jsx b/src/Components/Alert/index.jsx similarity index 100% rename from Client/src/Components/Alert/index.jsx rename to src/Components/Alert/index.jsx diff --git a/Client/src/Components/Animated/PulseDot.jsx b/src/Components/Animated/PulseDot.jsx similarity index 100% rename from Client/src/Components/Animated/PulseDot.jsx rename to src/Components/Animated/PulseDot.jsx diff --git a/Client/src/Components/Avatar/index.css b/src/Components/Avatar/index.css similarity index 100% rename from Client/src/Components/Avatar/index.css rename to src/Components/Avatar/index.css diff --git a/Client/src/Components/Avatar/index.jsx b/src/Components/Avatar/index.jsx similarity index 100% rename from Client/src/Components/Avatar/index.jsx rename to src/Components/Avatar/index.jsx diff --git a/Client/src/Components/Breadcrumbs/index.css b/src/Components/Breadcrumbs/index.css similarity index 100% rename from Client/src/Components/Breadcrumbs/index.css rename to src/Components/Breadcrumbs/index.css diff --git a/Client/src/Components/Breadcrumbs/index.jsx b/src/Components/Breadcrumbs/index.jsx similarity index 100% rename from Client/src/Components/Breadcrumbs/index.jsx rename to src/Components/Breadcrumbs/index.jsx diff --git a/Client/src/Components/Charts/AreaChart/index.jsx b/src/Components/Charts/AreaChart/index.jsx similarity index 100% rename from Client/src/Components/Charts/AreaChart/index.jsx rename to src/Components/Charts/AreaChart/index.jsx diff --git a/Client/src/Components/Charts/BarChart/index.css b/src/Components/Charts/BarChart/index.css similarity index 100% rename from Client/src/Components/Charts/BarChart/index.css rename to src/Components/Charts/BarChart/index.css diff --git a/Client/src/Components/Charts/BarChart/index.jsx b/src/Components/Charts/BarChart/index.jsx similarity index 100% rename from Client/src/Components/Charts/BarChart/index.jsx rename to src/Components/Charts/BarChart/index.jsx diff --git a/Client/src/Components/Charts/ChartBox/index.jsx b/src/Components/Charts/ChartBox/index.jsx similarity index 100% rename from Client/src/Components/Charts/ChartBox/index.jsx rename to src/Components/Charts/ChartBox/index.jsx diff --git a/Client/src/Components/Charts/CustomGauge/index.css b/src/Components/Charts/CustomGauge/index.css similarity index 100% rename from Client/src/Components/Charts/CustomGauge/index.css rename to src/Components/Charts/CustomGauge/index.css diff --git a/Client/src/Components/Charts/CustomGauge/index.jsx b/src/Components/Charts/CustomGauge/index.jsx similarity index 100% rename from Client/src/Components/Charts/CustomGauge/index.jsx rename to src/Components/Charts/CustomGauge/index.jsx diff --git a/Client/src/Components/Charts/LegendBox/index.jsx b/src/Components/Charts/LegendBox/index.jsx similarity index 100% rename from Client/src/Components/Charts/LegendBox/index.jsx rename to src/Components/Charts/LegendBox/index.jsx diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.css b/src/Components/Charts/MonitorDetailsAreaChart/index.css similarity index 100% rename from Client/src/Components/Charts/MonitorDetailsAreaChart/index.css rename to src/Components/Charts/MonitorDetailsAreaChart/index.css diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/src/Components/Charts/MonitorDetailsAreaChart/index.jsx similarity index 100% rename from Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx rename to src/Components/Charts/MonitorDetailsAreaChart/index.jsx diff --git a/Client/src/Components/Charts/StatusPageBarChart/index.jsx b/src/Components/Charts/StatusPageBarChart/index.jsx similarity index 100% rename from Client/src/Components/Charts/StatusPageBarChart/index.jsx rename to src/Components/Charts/StatusPageBarChart/index.jsx diff --git a/Client/src/Components/Charts/Utils/chartUtilFunctions.js b/src/Components/Charts/Utils/chartUtilFunctions.js similarity index 100% rename from Client/src/Components/Charts/Utils/chartUtilFunctions.js rename to src/Components/Charts/Utils/chartUtilFunctions.js diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/src/Components/Charts/Utils/chartUtils.jsx similarity index 100% rename from Client/src/Components/Charts/Utils/chartUtils.jsx rename to src/Components/Charts/Utils/chartUtils.jsx diff --git a/Client/src/Components/Charts/Utils/gradientUtils.jsx b/src/Components/Charts/Utils/gradientUtils.jsx similarity index 100% rename from Client/src/Components/Charts/Utils/gradientUtils.jsx rename to src/Components/Charts/Utils/gradientUtils.jsx diff --git a/Client/src/Components/Check/Check.jsx b/src/Components/Check/Check.jsx similarity index 100% rename from Client/src/Components/Check/Check.jsx rename to src/Components/Check/Check.jsx diff --git a/Client/src/Components/Check/check.css b/src/Components/Check/check.css similarity index 100% rename from Client/src/Components/Check/check.css rename to src/Components/Check/check.css diff --git a/Client/src/Components/CircularCount/index.jsx b/src/Components/CircularCount/index.jsx similarity index 100% rename from Client/src/Components/CircularCount/index.jsx rename to src/Components/CircularCount/index.jsx diff --git a/Client/src/Components/ConfigBox/index.jsx b/src/Components/ConfigBox/index.jsx similarity index 100% rename from Client/src/Components/ConfigBox/index.jsx rename to src/Components/ConfigBox/index.jsx diff --git a/Client/src/Components/Dialog/genericDialog.jsx b/src/Components/Dialog/genericDialog.jsx similarity index 100% rename from Client/src/Components/Dialog/genericDialog.jsx rename to src/Components/Dialog/genericDialog.jsx diff --git a/Client/src/Components/Dialog/index.jsx b/src/Components/Dialog/index.jsx similarity index 100% rename from Client/src/Components/Dialog/index.jsx rename to src/Components/Dialog/index.jsx diff --git a/Client/src/Components/Dot/index.jsx b/src/Components/Dot/index.jsx similarity index 100% rename from Client/src/Components/Dot/index.jsx rename to src/Components/Dot/index.jsx diff --git a/Client/src/Components/Fallback/index.css b/src/Components/Fallback/index.css similarity index 100% rename from Client/src/Components/Fallback/index.css rename to src/Components/Fallback/index.css diff --git a/Client/src/Components/Fallback/index.jsx b/src/Components/Fallback/index.jsx similarity index 100% rename from Client/src/Components/Fallback/index.jsx rename to src/Components/Fallback/index.jsx diff --git a/Client/src/Components/GenericFallback/NetworkError.jsx b/src/Components/GenericFallback/NetworkError.jsx similarity index 100% rename from Client/src/Components/GenericFallback/NetworkError.jsx rename to src/Components/GenericFallback/NetworkError.jsx diff --git a/Client/src/Components/GenericFallback/index.jsx b/src/Components/GenericFallback/index.jsx similarity index 100% rename from Client/src/Components/GenericFallback/index.jsx rename to src/Components/GenericFallback/index.jsx diff --git a/Client/src/Components/HOC/withAdminCheck.jsx b/src/Components/HOC/withAdminCheck.jsx similarity index 100% rename from Client/src/Components/HOC/withAdminCheck.jsx rename to src/Components/HOC/withAdminCheck.jsx diff --git a/Client/src/Components/Heading/index.jsx b/src/Components/Heading/index.jsx similarity index 100% rename from Client/src/Components/Heading/index.jsx rename to src/Components/Heading/index.jsx diff --git a/Client/src/Components/Host/index.jsx b/src/Components/Host/index.jsx similarity index 100% rename from Client/src/Components/Host/index.jsx rename to src/Components/Host/index.jsx diff --git a/Client/src/Components/HttpStatusLabel/index.jsx b/src/Components/HttpStatusLabel/index.jsx similarity index 100% rename from Client/src/Components/HttpStatusLabel/index.jsx rename to src/Components/HttpStatusLabel/index.jsx diff --git a/Client/src/Components/IconBox/index.jsx b/src/Components/IconBox/index.jsx similarity index 100% rename from Client/src/Components/IconBox/index.jsx rename to src/Components/IconBox/index.jsx diff --git a/Client/src/Components/Image/index.jsx b/src/Components/Image/index.jsx similarity index 100% rename from Client/src/Components/Image/index.jsx rename to src/Components/Image/index.jsx diff --git a/Client/src/Components/Inputs/Checkbox/index.css b/src/Components/Inputs/Checkbox/index.css similarity index 100% rename from Client/src/Components/Inputs/Checkbox/index.css rename to src/Components/Inputs/Checkbox/index.css diff --git a/Client/src/Components/Inputs/Checkbox/index.jsx b/src/Components/Inputs/Checkbox/index.jsx similarity index 100% rename from Client/src/Components/Inputs/Checkbox/index.jsx rename to src/Components/Inputs/Checkbox/index.jsx diff --git a/Client/src/Components/Inputs/ColorPicker/index.jsx b/src/Components/Inputs/ColorPicker/index.jsx similarity index 100% rename from Client/src/Components/Inputs/ColorPicker/index.jsx rename to src/Components/Inputs/ColorPicker/index.jsx diff --git a/Client/src/Components/Inputs/Image/index.css b/src/Components/Inputs/Image/index.css similarity index 100% rename from Client/src/Components/Inputs/Image/index.css rename to src/Components/Inputs/Image/index.css diff --git a/Client/src/Components/Inputs/Image/index.jsx b/src/Components/Inputs/Image/index.jsx similarity index 100% rename from Client/src/Components/Inputs/Image/index.jsx rename to src/Components/Inputs/Image/index.jsx diff --git a/Client/src/Components/Inputs/Radio/index.css b/src/Components/Inputs/Radio/index.css similarity index 100% rename from Client/src/Components/Inputs/Radio/index.css rename to src/Components/Inputs/Radio/index.css diff --git a/Client/src/Components/Inputs/Radio/index.jsx b/src/Components/Inputs/Radio/index.jsx similarity index 100% rename from Client/src/Components/Inputs/Radio/index.jsx rename to src/Components/Inputs/Radio/index.jsx diff --git a/Client/src/Components/Inputs/Search/index.jsx b/src/Components/Inputs/Search/index.jsx similarity index 100% rename from Client/src/Components/Inputs/Search/index.jsx rename to src/Components/Inputs/Search/index.jsx diff --git a/Client/src/Components/Inputs/Select/index.css b/src/Components/Inputs/Select/index.css similarity index 100% rename from Client/src/Components/Inputs/Select/index.css rename to src/Components/Inputs/Select/index.css diff --git a/Client/src/Components/Inputs/Select/index.jsx b/src/Components/Inputs/Select/index.jsx similarity index 100% rename from Client/src/Components/Inputs/Select/index.jsx rename to src/Components/Inputs/Select/index.jsx diff --git a/Client/src/Components/Inputs/TextInput/Adornments/index.jsx b/src/Components/Inputs/TextInput/Adornments/index.jsx similarity index 100% rename from Client/src/Components/Inputs/TextInput/Adornments/index.jsx rename to src/Components/Inputs/TextInput/Adornments/index.jsx diff --git a/Client/src/Components/Inputs/TextInput/index.jsx b/src/Components/Inputs/TextInput/index.jsx similarity index 100% rename from Client/src/Components/Inputs/TextInput/index.jsx rename to src/Components/Inputs/TextInput/index.jsx diff --git a/Client/src/Components/Label/index.css b/src/Components/Label/index.css similarity index 100% rename from Client/src/Components/Label/index.css rename to src/Components/Label/index.css diff --git a/Client/src/Components/Label/index.jsx b/src/Components/Label/index.jsx similarity index 100% rename from Client/src/Components/Label/index.jsx rename to src/Components/Label/index.jsx diff --git a/Client/src/Components/LanguageSelector.jsx b/src/Components/LanguageSelector.jsx similarity index 100% rename from Client/src/Components/LanguageSelector.jsx rename to src/Components/LanguageSelector.jsx diff --git a/Client/src/Components/Layouts/HomeLayout/index.css b/src/Components/Layouts/HomeLayout/index.css similarity index 100% rename from Client/src/Components/Layouts/HomeLayout/index.css rename to src/Components/Layouts/HomeLayout/index.css diff --git a/Client/src/Components/Layouts/HomeLayout/index.jsx b/src/Components/Layouts/HomeLayout/index.jsx similarity index 100% rename from Client/src/Components/Layouts/HomeLayout/index.jsx rename to src/Components/Layouts/HomeLayout/index.jsx diff --git a/Client/src/Components/Link/index.jsx b/src/Components/Link/index.jsx similarity index 100% rename from Client/src/Components/Link/index.jsx rename to src/Components/Link/index.jsx diff --git a/Client/src/Components/Link/link.css b/src/Components/Link/link.css similarity index 100% rename from Client/src/Components/Link/link.css rename to src/Components/Link/link.css diff --git a/Client/src/Components/MonitorCountHeader/index.jsx b/src/Components/MonitorCountHeader/index.jsx similarity index 100% rename from Client/src/Components/MonitorCountHeader/index.jsx rename to src/Components/MonitorCountHeader/index.jsx diff --git a/Client/src/Components/MonitorCountHeader/skeleton.jsx b/src/Components/MonitorCountHeader/skeleton.jsx similarity index 100% rename from Client/src/Components/MonitorCountHeader/skeleton.jsx rename to src/Components/MonitorCountHeader/skeleton.jsx diff --git a/Client/src/Components/MonitorCreateHeader/index.jsx b/src/Components/MonitorCreateHeader/index.jsx similarity index 100% rename from Client/src/Components/MonitorCreateHeader/index.jsx rename to src/Components/MonitorCreateHeader/index.jsx diff --git a/Client/src/Components/MonitorCreateHeader/skeleton.jsx b/src/Components/MonitorCreateHeader/skeleton.jsx similarity index 100% rename from Client/src/Components/MonitorCreateHeader/skeleton.jsx rename to src/Components/MonitorCreateHeader/skeleton.jsx diff --git a/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx b/src/Components/MonitorStatusHeader/ConfigButton/index.jsx similarity index 100% rename from Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx rename to src/Components/MonitorStatusHeader/ConfigButton/index.jsx diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/src/Components/MonitorStatusHeader/index.jsx similarity index 100% rename from Client/src/Components/MonitorStatusHeader/index.jsx rename to src/Components/MonitorStatusHeader/index.jsx diff --git a/Client/src/Components/MonitorStatusHeader/skeleton.jsx b/src/Components/MonitorStatusHeader/skeleton.jsx similarity index 100% rename from Client/src/Components/MonitorStatusHeader/skeleton.jsx rename to src/Components/MonitorStatusHeader/skeleton.jsx diff --git a/Client/src/Components/MonitorTimeFrameHeader/index.jsx b/src/Components/MonitorTimeFrameHeader/index.jsx similarity index 100% rename from Client/src/Components/MonitorTimeFrameHeader/index.jsx rename to src/Components/MonitorTimeFrameHeader/index.jsx diff --git a/Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx b/src/Components/MonitorTimeFrameHeader/skeleton.jsx similarity index 100% rename from Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx rename to src/Components/MonitorTimeFrameHeader/skeleton.jsx diff --git a/Client/src/Components/ProgressBars/index.css b/src/Components/ProgressBars/index.css similarity index 100% rename from Client/src/Components/ProgressBars/index.css rename to src/Components/ProgressBars/index.css diff --git a/Client/src/Components/ProgressBars/index.jsx b/src/Components/ProgressBars/index.jsx similarity index 100% rename from Client/src/Components/ProgressBars/index.jsx rename to src/Components/ProgressBars/index.jsx diff --git a/Client/src/Components/ProgressStepper/index.css b/src/Components/ProgressStepper/index.css similarity index 100% rename from Client/src/Components/ProgressStepper/index.css rename to src/Components/ProgressStepper/index.css diff --git a/Client/src/Components/ProgressStepper/index.jsx b/src/Components/ProgressStepper/index.jsx similarity index 100% rename from Client/src/Components/ProgressStepper/index.jsx rename to src/Components/ProgressStepper/index.jsx diff --git a/Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx b/src/Components/ProtectedDistributedUptimeRoute/index.jsx similarity index 100% rename from Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx rename to src/Components/ProtectedDistributedUptimeRoute/index.jsx diff --git a/Client/src/Components/ProtectedRoute/index.jsx b/src/Components/ProtectedRoute/index.jsx similarity index 100% rename from Client/src/Components/ProtectedRoute/index.jsx rename to src/Components/ProtectedRoute/index.jsx diff --git a/Client/src/Components/Sidebar/index.css b/src/Components/Sidebar/index.css similarity index 100% rename from Client/src/Components/Sidebar/index.css rename to src/Components/Sidebar/index.css diff --git a/Client/src/Components/Sidebar/index.jsx b/src/Components/Sidebar/index.jsx similarity index 100% rename from Client/src/Components/Sidebar/index.jsx rename to src/Components/Sidebar/index.jsx diff --git a/Client/src/Components/Skeletons/FullPage/index.jsx b/src/Components/Skeletons/FullPage/index.jsx similarity index 100% rename from Client/src/Components/Skeletons/FullPage/index.jsx rename to src/Components/Skeletons/FullPage/index.jsx diff --git a/Client/src/Components/StatBox/index.jsx b/src/Components/StatBox/index.jsx similarity index 100% rename from Client/src/Components/StatBox/index.jsx rename to src/Components/StatBox/index.jsx diff --git a/Client/src/Components/StatusBoxes/index.jsx b/src/Components/StatusBoxes/index.jsx similarity index 100% rename from Client/src/Components/StatusBoxes/index.jsx rename to src/Components/StatusBoxes/index.jsx diff --git a/Client/src/Components/StatusBoxes/skeleton.jsx b/src/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from Client/src/Components/StatusBoxes/skeleton.jsx rename to src/Components/StatusBoxes/skeleton.jsx diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/src/Components/TabPanels/Account/PasswordPanel.jsx similarity index 100% rename from Client/src/Components/TabPanels/Account/PasswordPanel.jsx rename to src/Components/TabPanels/Account/PasswordPanel.jsx diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/src/Components/TabPanels/Account/ProfilePanel.jsx similarity index 100% rename from Client/src/Components/TabPanels/Account/ProfilePanel.jsx rename to src/Components/TabPanels/Account/ProfilePanel.jsx diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/src/Components/TabPanels/Account/TeamPanel.jsx similarity index 100% rename from Client/src/Components/TabPanels/Account/TeamPanel.jsx rename to src/Components/TabPanels/Account/TeamPanel.jsx diff --git a/Client/src/Components/Table/TablePagination/Actions/index.jsx b/src/Components/Table/TablePagination/Actions/index.jsx similarity index 100% rename from Client/src/Components/Table/TablePagination/Actions/index.jsx rename to src/Components/Table/TablePagination/Actions/index.jsx diff --git a/Client/src/Components/Table/TablePagination/index.jsx b/src/Components/Table/TablePagination/index.jsx similarity index 100% rename from Client/src/Components/Table/TablePagination/index.jsx rename to src/Components/Table/TablePagination/index.jsx diff --git a/Client/src/Components/Table/index.jsx b/src/Components/Table/index.jsx similarity index 100% rename from Client/src/Components/Table/index.jsx rename to src/Components/Table/index.jsx diff --git a/Client/src/Components/Table/skeleton.jsx b/src/Components/Table/skeleton.jsx similarity index 100% rename from Client/src/Components/Table/skeleton.jsx rename to src/Components/Table/skeleton.jsx diff --git a/Client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx b/src/Components/ThemeSwitch/SunAndMoonIcon.jsx similarity index 100% rename from Client/src/Components/ThemeSwitch/SunAndMoonIcon.jsx rename to src/Components/ThemeSwitch/SunAndMoonIcon.jsx diff --git a/Client/src/Components/ThemeSwitch/index.css b/src/Components/ThemeSwitch/index.css similarity index 100% rename from Client/src/Components/ThemeSwitch/index.css rename to src/Components/ThemeSwitch/index.css diff --git a/Client/src/Components/ThemeSwitch/index.jsx b/src/Components/ThemeSwitch/index.jsx similarity index 100% rename from Client/src/Components/ThemeSwitch/index.jsx rename to src/Components/ThemeSwitch/index.jsx diff --git a/Client/src/Components/WalletProvider/index.jsx b/src/Components/WalletProvider/index.jsx similarity index 100% rename from Client/src/Components/WalletProvider/index.jsx rename to src/Components/WalletProvider/index.jsx diff --git a/Client/src/Features/Auth/authSlice.js b/src/Features/Auth/authSlice.js similarity index 100% rename from Client/src/Features/Auth/authSlice.js rename to src/Features/Auth/authSlice.js diff --git a/Client/src/Features/InfrastructureMonitors/infrastructureMonitorsSlice.js b/src/Features/InfrastructureMonitors/infrastructureMonitorsSlice.js similarity index 100% rename from Client/src/Features/InfrastructureMonitors/infrastructureMonitorsSlice.js rename to src/Features/InfrastructureMonitors/infrastructureMonitorsSlice.js diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js similarity index 100% rename from Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js rename to src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js diff --git a/Client/src/Features/Settings/settingsSlice.js b/src/Features/Settings/settingsSlice.js similarity index 100% rename from Client/src/Features/Settings/settingsSlice.js rename to src/Features/Settings/settingsSlice.js diff --git a/Client/src/Features/UI/uiSlice.js b/src/Features/UI/uiSlice.js similarity index 100% rename from Client/src/Features/UI/uiSlice.js rename to src/Features/UI/uiSlice.js diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/src/Features/UptimeMonitors/uptimeMonitorsSlice.js similarity index 100% rename from Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js rename to src/Features/UptimeMonitors/uptimeMonitorsSlice.js diff --git a/Client/src/Hooks/useIsAdmin.js b/src/Hooks/useIsAdmin.js similarity index 100% rename from Client/src/Hooks/useIsAdmin.js rename to src/Hooks/useIsAdmin.js diff --git a/Client/src/Hooks/useMonitorUtils.js b/src/Hooks/useMonitorUtils.js similarity index 100% rename from Client/src/Hooks/useMonitorUtils.js rename to src/Hooks/useMonitorUtils.js diff --git a/Client/src/Pages/Account/index.css b/src/Pages/Account/index.css similarity index 100% rename from Client/src/Pages/Account/index.css rename to src/Pages/Account/index.css diff --git a/Client/src/Pages/Account/index.jsx b/src/Pages/Account/index.jsx similarity index 100% rename from Client/src/Pages/Account/index.jsx rename to src/Pages/Account/index.jsx diff --git a/Client/src/Pages/Auth/CheckEmail.jsx b/src/Pages/Auth/CheckEmail.jsx similarity index 100% rename from Client/src/Pages/Auth/CheckEmail.jsx rename to src/Pages/Auth/CheckEmail.jsx diff --git a/Client/src/Pages/Auth/ForgotPassword.jsx b/src/Pages/Auth/ForgotPassword.jsx similarity index 100% rename from Client/src/Pages/Auth/ForgotPassword.jsx rename to src/Pages/Auth/ForgotPassword.jsx diff --git a/Client/src/Pages/Auth/Login/Components/EmailStep.jsx b/src/Pages/Auth/Login/Components/EmailStep.jsx similarity index 100% rename from Client/src/Pages/Auth/Login/Components/EmailStep.jsx rename to src/Pages/Auth/Login/Components/EmailStep.jsx diff --git a/Client/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx b/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx similarity index 100% rename from Client/src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx rename to src/Pages/Auth/Login/Components/ForgotPasswordLabel.jsx diff --git a/Client/src/Pages/Auth/Login/Components/PasswordStep.jsx b/src/Pages/Auth/Login/Components/PasswordStep.jsx similarity index 100% rename from Client/src/Pages/Auth/Login/Components/PasswordStep.jsx rename to src/Pages/Auth/Login/Components/PasswordStep.jsx diff --git a/Client/src/Pages/Auth/Login/Login.jsx b/src/Pages/Auth/Login/Login.jsx similarity index 100% rename from Client/src/Pages/Auth/Login/Login.jsx rename to src/Pages/Auth/Login/Login.jsx diff --git a/Client/src/Pages/Auth/NewPasswordConfirmed.jsx b/src/Pages/Auth/NewPasswordConfirmed.jsx similarity index 100% rename from Client/src/Pages/Auth/NewPasswordConfirmed.jsx rename to src/Pages/Auth/NewPasswordConfirmed.jsx diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/src/Pages/Auth/Register/Register.jsx similarity index 100% rename from Client/src/Pages/Auth/Register/Register.jsx rename to src/Pages/Auth/Register/Register.jsx diff --git a/Client/src/Pages/Auth/Register/StepOne/index.jsx b/src/Pages/Auth/Register/StepOne/index.jsx similarity index 100% rename from Client/src/Pages/Auth/Register/StepOne/index.jsx rename to src/Pages/Auth/Register/StepOne/index.jsx diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/src/Pages/Auth/Register/StepThree/index.jsx similarity index 100% rename from Client/src/Pages/Auth/Register/StepThree/index.jsx rename to src/Pages/Auth/Register/StepThree/index.jsx diff --git a/Client/src/Pages/Auth/Register/StepTwo/index.jsx b/src/Pages/Auth/Register/StepTwo/index.jsx similarity index 100% rename from Client/src/Pages/Auth/Register/StepTwo/index.jsx rename to src/Pages/Auth/Register/StepTwo/index.jsx diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/src/Pages/Auth/SetNewPassword.jsx similarity index 100% rename from Client/src/Pages/Auth/SetNewPassword.jsx rename to src/Pages/Auth/SetNewPassword.jsx diff --git a/Client/src/Pages/Auth/hooks/useValidatePassword.jsx b/src/Pages/Auth/hooks/useValidatePassword.jsx similarity index 100% rename from Client/src/Pages/Auth/hooks/useValidatePassword.jsx rename to src/Pages/Auth/hooks/useValidatePassword.jsx diff --git a/Client/src/Pages/Auth/index.css b/src/Pages/Auth/index.css similarity index 100% rename from Client/src/Pages/Auth/index.css rename to src/Pages/Auth/index.css diff --git a/Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx b/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx rename to src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx diff --git a/Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx b/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx rename to src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx diff --git a/Client/src/Pages/DistributedUptime/Create/index.jsx b/src/Pages/DistributedUptime/Create/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Create/index.jsx rename to src/Pages/DistributedUptime/Create/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/ControlsHeader/index.jsx b/src/Pages/DistributedUptime/Details/Components/ControlsHeader/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/ControlsHeader/index.jsx rename to src/Pages/DistributedUptime/Details/Components/ControlsHeader/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx b/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx rename to src/Pages/DistributedUptime/Details/Components/DeviceTicker/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/DistributedUptimeMapStyle.json b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/DistributedUptimeMapStyle.json similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/DistributedUptimeMapStyle.json rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/DistributedUptimeMapStyle.json diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/buildStyle.js b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/buildStyle.js similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/buildStyle.js rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/buildStyle.js diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeMap/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Area/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Area/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Area/index.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Area/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Bar/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Bar/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Bar/index.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Bar/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/Tick.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/Tick.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/Tick.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/Tick.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/ToolTip.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/ToolTip.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/ToolTip.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/Helpers/ToolTip.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx b/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx rename to src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/Footer/index.jsx b/src/Pages/DistributedUptime/Details/Components/Footer/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/Footer/index.jsx rename to src/Pages/DistributedUptime/Details/Components/Footer/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/LastUpdate/index.jsx b/src/Pages/DistributedUptime/Details/Components/LastUpdate/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/LastUpdate/index.jsx rename to src/Pages/DistributedUptime/Details/Components/LastUpdate/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx b/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx rename to src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/NextExpectedCheck/index.jsx b/src/Pages/DistributedUptime/Details/Components/NextExpectedCheck/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/NextExpectedCheck/index.jsx rename to src/Pages/DistributedUptime/Details/Components/NextExpectedCheck/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx b/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx rename to src/Pages/DistributedUptime/Details/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx b/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx rename to src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx b/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx rename to src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx b/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx rename to src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/src/Pages/DistributedUptime/Details/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Details/index.jsx rename to src/Pages/DistributedUptime/Details/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx b/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx rename to src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx b/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx rename to src/Pages/DistributedUptime/Monitors/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx b/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx rename to src/Pages/DistributedUptime/Monitors/Hooks/useSubscribeToMonitors.jsx diff --git a/Client/src/Pages/DistributedUptime/Monitors/index.jsx b/src/Pages/DistributedUptime/Monitors/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptime/Monitors/index.jsx rename to src/Pages/DistributedUptime/Monitors/index.jsx diff --git a/Client/src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx b/src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx rename to src/Pages/DistributedUptimeStatus/Create/Components/VisuallyHiddenInput/index.jsx diff --git a/Client/src/Pages/DistributedUptimeStatus/Create/index.jsx b/src/Pages/DistributedUptimeStatus/Create/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptimeStatus/Create/index.jsx rename to src/Pages/DistributedUptimeStatus/Create/index.jsx diff --git a/Client/src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx b/src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx rename to src/Pages/DistributedUptimeStatus/Status/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx b/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx similarity index 100% rename from Client/src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx rename to src/Pages/DistributedUptimeStatus/Status/Hooks/useStatusPageFetchByUrl.jsx diff --git a/Client/src/Pages/DistributedUptimeStatus/Status/index.jsx b/src/Pages/DistributedUptimeStatus/Status/index.jsx similarity index 100% rename from Client/src/Pages/DistributedUptimeStatus/Status/index.jsx rename to src/Pages/DistributedUptimeStatus/Status/index.jsx diff --git a/Client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/src/Pages/Incidents/Components/IncidentTable/index.jsx similarity index 100% rename from Client/src/Pages/Incidents/Components/IncidentTable/index.jsx rename to src/Pages/Incidents/Components/IncidentTable/index.jsx diff --git a/Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/src/Pages/Incidents/Components/OptionsHeader/index.jsx similarity index 100% rename from Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx rename to src/Pages/Incidents/Components/OptionsHeader/index.jsx diff --git a/Client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx b/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx similarity index 100% rename from Client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx rename to src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx diff --git a/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx b/src/Pages/Incidents/Hooks/useChecksFetch.jsx similarity index 100% rename from Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx rename to src/Pages/Incidents/Hooks/useChecksFetch.jsx diff --git a/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx b/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx rename to src/Pages/Incidents/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/Incidents/index.jsx b/src/Pages/Incidents/index.jsx similarity index 100% rename from Client/src/Pages/Incidents/index.jsx rename to src/Pages/Incidents/index.jsx diff --git a/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx b/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx rename to src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx diff --git a/Client/src/Pages/Infrastructure/Create/index.jsx b/src/Pages/Infrastructure/Create/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Create/index.jsx rename to src/Pages/Infrastructure/Create/index.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx rename to src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx rename to src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx rename to src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx rename to src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx rename to src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx rename to src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx rename to src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx rename to src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx b/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx rename to src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx diff --git a/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx rename to src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/src/Pages/Infrastructure/Details/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Details/index.jsx rename to src/Pages/Infrastructure/Details/index.jsx diff --git a/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename to src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx diff --git a/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx b/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx rename to src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx diff --git a/Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx b/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx rename to src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx diff --git a/Client/src/Pages/Infrastructure/Monitors/index.jsx b/src/Pages/Infrastructure/Monitors/index.jsx similarity index 100% rename from Client/src/Pages/Infrastructure/Monitors/index.jsx rename to src/Pages/Infrastructure/Monitors/index.jsx diff --git a/Client/src/Pages/Integrations/index.css b/src/Pages/Integrations/index.css similarity index 100% rename from Client/src/Pages/Integrations/index.css rename to src/Pages/Integrations/index.css diff --git a/Client/src/Pages/Integrations/index.jsx b/src/Pages/Integrations/index.jsx similarity index 100% rename from Client/src/Pages/Integrations/index.jsx rename to src/Pages/Integrations/index.jsx diff --git a/Client/src/Pages/Maintenance/CreateMaintenance/index.css b/src/Pages/Maintenance/CreateMaintenance/index.css similarity index 100% rename from Client/src/Pages/Maintenance/CreateMaintenance/index.css rename to src/Pages/Maintenance/CreateMaintenance/index.css diff --git a/Client/src/Pages/Maintenance/CreateMaintenance/index.jsx b/src/Pages/Maintenance/CreateMaintenance/index.jsx similarity index 100% rename from Client/src/Pages/Maintenance/CreateMaintenance/index.jsx rename to src/Pages/Maintenance/CreateMaintenance/index.jsx diff --git a/Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx b/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx similarity index 100% rename from Client/src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx rename to src/Pages/Maintenance/MaintenanceTable/ActionsMenu/index.jsx diff --git a/Client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/src/Pages/Maintenance/MaintenanceTable/index.jsx similarity index 100% rename from Client/src/Pages/Maintenance/MaintenanceTable/index.jsx rename to src/Pages/Maintenance/MaintenanceTable/index.jsx diff --git a/Client/src/Pages/Maintenance/index.css b/src/Pages/Maintenance/index.css similarity index 100% rename from Client/src/Pages/Maintenance/index.css rename to src/Pages/Maintenance/index.css diff --git a/Client/src/Pages/Maintenance/index.jsx b/src/Pages/Maintenance/index.jsx similarity index 100% rename from Client/src/Pages/Maintenance/index.jsx rename to src/Pages/Maintenance/index.jsx diff --git a/Client/src/Pages/NotFound/index.jsx b/src/Pages/NotFound/index.jsx similarity index 100% rename from Client/src/Pages/NotFound/index.jsx rename to src/Pages/NotFound/index.jsx diff --git a/Client/src/Pages/PageSpeed/Configure/index.css b/src/Pages/PageSpeed/Configure/index.css similarity index 100% rename from Client/src/Pages/PageSpeed/Configure/index.css rename to src/Pages/PageSpeed/Configure/index.css diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/src/Pages/PageSpeed/Configure/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Configure/index.jsx rename to src/Pages/PageSpeed/Configure/index.jsx diff --git a/Client/src/Pages/PageSpeed/Configure/skeleton.jsx b/src/Pages/PageSpeed/Configure/skeleton.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Configure/skeleton.jsx rename to src/Pages/PageSpeed/Configure/skeleton.jsx diff --git a/Client/src/Pages/PageSpeed/Create/index.css b/src/Pages/PageSpeed/Create/index.css similarity index 100% rename from Client/src/Pages/PageSpeed/Create/index.css rename to src/Pages/PageSpeed/Create/index.css diff --git a/Client/src/Pages/PageSpeed/Create/index.jsx b/src/Pages/PageSpeed/Create/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Create/index.jsx rename to src/Pages/PageSpeed/Create/index.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx b/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx rename to src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename to src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx b/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx rename to src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx rename to src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx rename to src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx rename to src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx rename to src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx rename to src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx rename to src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx b/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx rename to src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/src/Pages/PageSpeed/Details/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Details/index.jsx rename to src/Pages/PageSpeed/Details/index.jsx diff --git a/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx rename to src/Pages/PageSpeed/Monitors/Components/Card/index.jsx diff --git a/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx rename to src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx diff --git a/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx b/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx rename to src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/src/Pages/PageSpeed/Monitors/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/Monitors/index.jsx rename to src/Pages/PageSpeed/Monitors/index.jsx diff --git a/Client/src/Pages/Settings/index.jsx b/src/Pages/Settings/index.jsx similarity index 100% rename from Client/src/Pages/Settings/index.jsx rename to src/Pages/Settings/index.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx b/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/MonitorList/index.jsx rename to src/Pages/StatusPage/Create/Components/MonitorList/index.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/Progress/index.jsx b/src/Pages/StatusPage/Create/Components/Progress/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/Progress/index.jsx rename to src/Pages/StatusPage/Create/Components/Progress/index.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx b/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/Skeleton/index.jsx rename to src/Pages/StatusPage/Create/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx b/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/Tabs/Content.jsx rename to src/Pages/StatusPage/Create/Components/Tabs/Content.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx b/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx rename to src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx diff --git a/Client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx b/src/Pages/StatusPage/Create/Components/Tabs/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Components/Tabs/index.jsx rename to src/Pages/StatusPage/Create/Components/Tabs/index.jsx diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx rename to src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx diff --git a/Client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx b/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx rename to src/Pages/StatusPage/Create/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/StatusPage/Create/index.jsx b/src/Pages/StatusPage/Create/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Create/index.jsx rename to src/Pages/StatusPage/Create/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx b/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Components/AdminLink/index.jsx rename to src/Pages/StatusPage/Status/Components/AdminLink/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx b/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx rename to src/Pages/StatusPage/Status/Components/ControlsHeader/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx b/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx rename to src/Pages/StatusPage/Status/Components/MonitorsList/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx b/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Components/Skeleton/index.jsx rename to src/Pages/StatusPage/Status/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx b/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Components/StatusBar/index.jsx rename to src/Pages/StatusPage/Status/Components/StatusBar/index.jsx diff --git a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx b/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx rename to src/Pages/StatusPage/Status/Hooks/useStatusPageDelete.jsx diff --git a/Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx b/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx rename to src/Pages/StatusPage/Status/Hooks/useStatusPageFetch.jsx diff --git a/Client/src/Pages/StatusPage/Status/index.jsx b/src/Pages/StatusPage/Status/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/Status/index.jsx rename to src/Pages/StatusPage/Status/index.jsx diff --git a/Client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx b/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx rename to src/Pages/StatusPage/StatusPages/Components/StatusPagesTable/index.jsx diff --git a/Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx similarity index 100% rename from Client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx rename to src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx diff --git a/Client/src/Pages/StatusPage/StatusPages/index.jsx b/src/Pages/StatusPage/StatusPages/index.jsx similarity index 100% rename from Client/src/Pages/StatusPage/StatusPages/index.jsx rename to src/Pages/StatusPage/StatusPages/index.jsx diff --git a/Client/src/Pages/Uptime/Configure/index.css b/src/Pages/Uptime/Configure/index.css similarity index 100% rename from Client/src/Pages/Uptime/Configure/index.css rename to src/Pages/Uptime/Configure/index.css diff --git a/Client/src/Pages/Uptime/Configure/index.jsx b/src/Pages/Uptime/Configure/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Configure/index.jsx rename to src/Pages/Uptime/Configure/index.jsx diff --git a/Client/src/Pages/Uptime/Configure/skeleton.jsx b/src/Pages/Uptime/Configure/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Configure/skeleton.jsx rename to src/Pages/Uptime/Configure/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Create/index.css b/src/Pages/Uptime/Create/index.css similarity index 100% rename from Client/src/Pages/Uptime/Create/index.css rename to src/Pages/Uptime/Create/index.css diff --git a/Client/src/Pages/Uptime/Create/index.jsx b/src/Pages/Uptime/Create/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Create/index.jsx rename to src/Pages/Uptime/Create/index.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx rename to src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx b/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename to src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx b/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx rename to src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx b/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx rename to src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx b/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx rename to src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx rename to src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename to src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx b/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx rename to src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx rename to src/Pages/Uptime/Details/Components/ResponseTable/index.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx rename to src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx rename to src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx diff --git a/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx rename to src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx diff --git a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx b/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx rename to src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx diff --git a/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx b/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx rename to src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/src/Pages/Uptime/Details/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/index.jsx rename to src/Pages/Uptime/Details/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx b/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx rename to src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx rename to src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx b/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx rename to src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx b/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx rename to src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx b/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx rename to src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx b/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx rename to src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx rename to src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx b/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx rename to src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx b/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx rename to src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx b/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx rename to src/Pages/Uptime/Monitors/Hooks/useUtils.jsx diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/src/Pages/Uptime/Monitors/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Monitors/index.jsx rename to src/Pages/Uptime/Monitors/index.jsx diff --git a/Client/src/Routes/index.jsx b/src/Routes/index.jsx similarity index 100% rename from Client/src/Routes/index.jsx rename to src/Routes/index.jsx diff --git a/Client/src/Utils/Logger.js b/src/Utils/Logger.js similarity index 100% rename from Client/src/Utils/Logger.js rename to src/Utils/Logger.js diff --git a/Client/src/Utils/NetworkService.js b/src/Utils/NetworkService.js similarity index 100% rename from Client/src/Utils/NetworkService.js rename to src/Utils/NetworkService.js diff --git a/Client/src/Utils/NetworkServiceProvider.jsx b/src/Utils/NetworkServiceProvider.jsx similarity index 100% rename from Client/src/Utils/NetworkServiceProvider.jsx rename to src/Utils/NetworkServiceProvider.jsx diff --git a/Client/src/Utils/ReadMe.md b/src/Utils/ReadMe.md similarity index 100% rename from Client/src/Utils/ReadMe.md rename to src/Utils/ReadMe.md diff --git a/Client/src/Utils/Theme/constants.js b/src/Utils/Theme/constants.js similarity index 100% rename from Client/src/Utils/Theme/constants.js rename to src/Utils/Theme/constants.js diff --git a/Client/src/Utils/Theme/darkTheme.js b/src/Utils/Theme/darkTheme.js similarity index 100% rename from Client/src/Utils/Theme/darkTheme.js rename to src/Utils/Theme/darkTheme.js diff --git a/Client/src/Utils/Theme/extractColorObject.js b/src/Utils/Theme/extractColorObject.js similarity index 100% rename from Client/src/Utils/Theme/extractColorObject.js rename to src/Utils/Theme/extractColorObject.js diff --git a/Client/src/Utils/Theme/globalTheme.js b/src/Utils/Theme/globalTheme.js similarity index 100% rename from Client/src/Utils/Theme/globalTheme.js rename to src/Utils/Theme/globalTheme.js diff --git a/Client/src/Utils/Theme/lightTheme.js b/src/Utils/Theme/lightTheme.js similarity index 100% rename from Client/src/Utils/Theme/lightTheme.js rename to src/Utils/Theme/lightTheme.js diff --git a/Client/src/Utils/debounce.jsx b/src/Utils/debounce.jsx similarity index 100% rename from Client/src/Utils/debounce.jsx rename to src/Utils/debounce.jsx diff --git a/Client/src/Utils/fileUtils.js b/src/Utils/fileUtils.js similarity index 100% rename from Client/src/Utils/fileUtils.js rename to src/Utils/fileUtils.js diff --git a/Client/src/Utils/greeting.jsx b/src/Utils/greeting.jsx similarity index 100% rename from Client/src/Utils/greeting.jsx rename to src/Utils/greeting.jsx diff --git a/Client/src/Utils/i18n.js b/src/Utils/i18n.js similarity index 100% rename from Client/src/Utils/i18n.js rename to src/Utils/i18n.js diff --git a/Client/src/Utils/monitorUtils.js b/src/Utils/monitorUtils.js similarity index 100% rename from Client/src/Utils/monitorUtils.js rename to src/Utils/monitorUtils.js diff --git a/Client/src/Utils/stringUtils.js b/src/Utils/stringUtils.js similarity index 100% rename from Client/src/Utils/stringUtils.js rename to src/Utils/stringUtils.js diff --git a/Client/src/Utils/timeUtils.js b/src/Utils/timeUtils.js similarity index 100% rename from Client/src/Utils/timeUtils.js rename to src/Utils/timeUtils.js diff --git a/Client/src/Utils/timezones.json b/src/Utils/timezones.json similarity index 100% rename from Client/src/Utils/timezones.json rename to src/Utils/timezones.json diff --git a/Client/src/Utils/toastUtils.jsx b/src/Utils/toastUtils.jsx similarity index 100% rename from Client/src/Utils/toastUtils.jsx rename to src/Utils/toastUtils.jsx diff --git a/Client/src/Validation/error.js b/src/Validation/error.js similarity index 100% rename from Client/src/Validation/error.js rename to src/Validation/error.js diff --git a/Client/src/Validation/validation.js b/src/Validation/validation.js similarity index 100% rename from Client/src/Validation/validation.js rename to src/Validation/validation.js diff --git a/Client/src/assets/Images/Google.png b/src/assets/Images/Google.png similarity index 100% rename from Client/src/assets/Images/Google.png rename to src/assets/Images/Google.png diff --git a/Client/src/assets/Images/avatar_placeholder.png b/src/assets/Images/avatar_placeholder.png similarity index 100% rename from Client/src/assets/Images/avatar_placeholder.png rename to src/assets/Images/avatar_placeholder.png diff --git a/Client/src/assets/Images/background-grid.svg b/src/assets/Images/background-grid.svg similarity index 100% rename from Client/src/assets/Images/background-grid.svg rename to src/assets/Images/background-grid.svg diff --git a/Client/src/assets/Images/bwl-logo.svg b/src/assets/Images/bwl-logo.svg similarity index 100% rename from Client/src/assets/Images/bwl-logo.svg rename to src/assets/Images/bwl-logo.svg diff --git a/Client/src/assets/Images/create-placeholder-dark.svg b/src/assets/Images/create-placeholder-dark.svg similarity index 100% rename from Client/src/assets/Images/create-placeholder-dark.svg rename to src/assets/Images/create-placeholder-dark.svg diff --git a/Client/src/assets/Images/create-placeholder.svg b/src/assets/Images/create-placeholder.svg similarity index 100% rename from Client/src/assets/Images/create-placeholder.svg rename to src/assets/Images/create-placeholder.svg diff --git a/Client/src/assets/Images/data_placeholder.svg b/src/assets/Images/data_placeholder.svg similarity index 100% rename from Client/src/assets/Images/data_placeholder.svg rename to src/assets/Images/data_placeholder.svg diff --git a/Client/src/assets/Images/data_placeholder_dark.svg b/src/assets/Images/data_placeholder_dark.svg similarity index 100% rename from Client/src/assets/Images/data_placeholder_dark.svg rename to src/assets/Images/data_placeholder_dark.svg diff --git a/Client/src/assets/Images/logo_placeholder.svg b/src/assets/Images/logo_placeholder.svg similarity index 100% rename from Client/src/assets/Images/logo_placeholder.svg rename to src/assets/Images/logo_placeholder.svg diff --git a/Client/src/assets/Images/sushi_404.svg b/src/assets/Images/sushi_404.svg similarity index 100% rename from Client/src/assets/Images/sushi_404.svg rename to src/assets/Images/sushi_404.svg diff --git a/Client/src/assets/icons/average-response-icon.svg b/src/assets/icons/average-response-icon.svg similarity index 100% rename from Client/src/assets/icons/average-response-icon.svg rename to src/assets/icons/average-response-icon.svg diff --git a/Client/src/assets/icons/calendar-check.svg b/src/assets/icons/calendar-check.svg similarity index 100% rename from Client/src/assets/icons/calendar-check.svg rename to src/assets/icons/calendar-check.svg diff --git a/Client/src/assets/icons/calendar.svg b/src/assets/icons/calendar.svg similarity index 100% rename from Client/src/assets/icons/calendar.svg rename to src/assets/icons/calendar.svg diff --git a/Client/src/assets/icons/certificate.svg b/src/assets/icons/certificate.svg similarity index 100% rename from Client/src/assets/icons/certificate.svg rename to src/assets/icons/certificate.svg diff --git a/Client/src/assets/icons/changeLog.svg b/src/assets/icons/changeLog.svg similarity index 100% rename from Client/src/assets/icons/changeLog.svg rename to src/assets/icons/changeLog.svg diff --git a/Client/src/assets/icons/check-outlined.svg b/src/assets/icons/check-outlined.svg similarity index 100% rename from Client/src/assets/icons/check-outlined.svg rename to src/assets/icons/check-outlined.svg diff --git a/Client/src/assets/icons/check.svg b/src/assets/icons/check.svg similarity index 100% rename from Client/src/assets/icons/check.svg rename to src/assets/icons/check.svg diff --git a/Client/src/assets/icons/checkbox-filled.svg b/src/assets/icons/checkbox-filled.svg similarity index 100% rename from Client/src/assets/icons/checkbox-filled.svg rename to src/assets/icons/checkbox-filled.svg diff --git a/Client/src/assets/icons/checkbox-green.svg b/src/assets/icons/checkbox-green.svg similarity index 100% rename from Client/src/assets/icons/checkbox-green.svg rename to src/assets/icons/checkbox-green.svg diff --git a/Client/src/assets/icons/checkbox-outline.svg b/src/assets/icons/checkbox-outline.svg similarity index 100% rename from Client/src/assets/icons/checkbox-outline.svg rename to src/assets/icons/checkbox-outline.svg diff --git a/Client/src/assets/icons/checkbox-red.svg b/src/assets/icons/checkbox-red.svg similarity index 100% rename from Client/src/assets/icons/checkbox-red.svg rename to src/assets/icons/checkbox-red.svg diff --git a/Client/src/assets/icons/checkmate-icon.svg b/src/assets/icons/checkmate-icon.svg similarity index 100% rename from Client/src/assets/icons/checkmate-icon.svg rename to src/assets/icons/checkmate-icon.svg diff --git a/Client/src/assets/icons/clock-snooze.svg b/src/assets/icons/clock-snooze.svg similarity index 100% rename from Client/src/assets/icons/clock-snooze.svg rename to src/assets/icons/clock-snooze.svg diff --git a/Client/src/assets/icons/cpu-chip.svg b/src/assets/icons/cpu-chip.svg similarity index 100% rename from Client/src/assets/icons/cpu-chip.svg rename to src/assets/icons/cpu-chip.svg diff --git a/Client/src/assets/icons/dashboard.svg b/src/assets/icons/dashboard.svg similarity index 100% rename from Client/src/assets/icons/dashboard.svg rename to src/assets/icons/dashboard.svg diff --git a/Client/src/assets/icons/discord-icon.svg b/src/assets/icons/discord-icon.svg similarity index 100% rename from Client/src/assets/icons/discord-icon.svg rename to src/assets/icons/discord-icon.svg diff --git a/Client/src/assets/icons/distributed-uptime.svg b/src/assets/icons/distributed-uptime.svg similarity index 100% rename from Client/src/assets/icons/distributed-uptime.svg rename to src/assets/icons/distributed-uptime.svg diff --git a/Client/src/assets/icons/docs.svg b/src/assets/icons/docs.svg similarity index 100% rename from Client/src/assets/icons/docs.svg rename to src/assets/icons/docs.svg diff --git a/Client/src/assets/icons/dots-vertical.svg b/src/assets/icons/dots-vertical.svg similarity index 100% rename from Client/src/assets/icons/dots-vertical.svg rename to src/assets/icons/dots-vertical.svg diff --git a/Client/src/assets/icons/down-arrow.svg b/src/assets/icons/down-arrow.svg similarity index 100% rename from Client/src/assets/icons/down-arrow.svg rename to src/assets/icons/down-arrow.svg diff --git a/Client/src/assets/icons/edit.svg b/src/assets/icons/edit.svg similarity index 100% rename from Client/src/assets/icons/edit.svg rename to src/assets/icons/edit.svg diff --git a/Client/src/assets/icons/email.svg b/src/assets/icons/email.svg similarity index 100% rename from Client/src/assets/icons/email.svg rename to src/assets/icons/email.svg diff --git a/Client/src/assets/icons/folder.svg b/src/assets/icons/folder.svg similarity index 100% rename from Client/src/assets/icons/folder.svg rename to src/assets/icons/folder.svg diff --git a/Client/src/assets/icons/history-icon.svg b/src/assets/icons/history-icon.svg similarity index 100% rename from Client/src/assets/icons/history-icon.svg rename to src/assets/icons/history-icon.svg diff --git a/Client/src/assets/icons/incidents.svg b/src/assets/icons/incidents.svg similarity index 100% rename from Client/src/assets/icons/incidents.svg rename to src/assets/icons/incidents.svg diff --git a/Client/src/assets/icons/integrations.svg b/src/assets/icons/integrations.svg similarity index 100% rename from Client/src/assets/icons/integrations.svg rename to src/assets/icons/integrations.svg diff --git a/Client/src/assets/icons/interval-check.svg b/src/assets/icons/interval-check.svg similarity index 100% rename from Client/src/assets/icons/interval-check.svg rename to src/assets/icons/interval-check.svg diff --git a/Client/src/assets/icons/key.svg b/src/assets/icons/key.svg similarity index 100% rename from Client/src/assets/icons/key.svg rename to src/assets/icons/key.svg diff --git a/Client/src/assets/icons/left-arrow-double.svg b/src/assets/icons/left-arrow-double.svg similarity index 100% rename from Client/src/assets/icons/left-arrow-double.svg rename to src/assets/icons/left-arrow-double.svg diff --git a/Client/src/assets/icons/left-arrow-long.svg b/src/assets/icons/left-arrow-long.svg similarity index 100% rename from Client/src/assets/icons/left-arrow-long.svg rename to src/assets/icons/left-arrow-long.svg diff --git a/Client/src/assets/icons/left-arrow.svg b/src/assets/icons/left-arrow.svg similarity index 100% rename from Client/src/assets/icons/left-arrow.svg rename to src/assets/icons/left-arrow.svg diff --git a/Client/src/assets/icons/lock.svg b/src/assets/icons/lock.svg similarity index 100% rename from Client/src/assets/icons/lock.svg rename to src/assets/icons/lock.svg diff --git a/Client/src/assets/icons/logout.svg b/src/assets/icons/logout.svg similarity index 100% rename from Client/src/assets/icons/logout.svg rename to src/assets/icons/logout.svg diff --git a/Client/src/assets/icons/mail.svg b/src/assets/icons/mail.svg similarity index 100% rename from Client/src/assets/icons/mail.svg rename to src/assets/icons/mail.svg diff --git a/Client/src/assets/icons/maintenance.svg b/src/assets/icons/maintenance.svg similarity index 100% rename from Client/src/assets/icons/maintenance.svg rename to src/assets/icons/maintenance.svg diff --git a/Client/src/assets/icons/monitor-graph-line.svg b/src/assets/icons/monitor-graph-line.svg similarity index 100% rename from Client/src/assets/icons/monitor-graph-line.svg rename to src/assets/icons/monitor-graph-line.svg diff --git a/Client/src/assets/icons/monitors.svg b/src/assets/icons/monitors.svg similarity index 100% rename from Client/src/assets/icons/monitors.svg rename to src/assets/icons/monitors.svg diff --git a/Client/src/assets/icons/open-in-new-page.svg b/src/assets/icons/open-in-new-page.svg similarity index 100% rename from Client/src/assets/icons/open-in-new-page.svg rename to src/assets/icons/open-in-new-page.svg diff --git a/Client/src/assets/icons/page-speed.svg b/src/assets/icons/page-speed.svg similarity index 100% rename from Client/src/assets/icons/page-speed.svg rename to src/assets/icons/page-speed.svg diff --git a/Client/src/assets/icons/pause-icon.svg b/src/assets/icons/pause-icon.svg similarity index 100% rename from Client/src/assets/icons/pause-icon.svg rename to src/assets/icons/pause-icon.svg diff --git a/Client/src/assets/icons/performance-report.svg b/src/assets/icons/performance-report.svg similarity index 100% rename from Client/src/assets/icons/performance-report.svg rename to src/assets/icons/performance-report.svg diff --git a/Client/src/assets/icons/radio-checked.svg b/src/assets/icons/radio-checked.svg similarity index 100% rename from Client/src/assets/icons/radio-checked.svg rename to src/assets/icons/radio-checked.svg diff --git a/Client/src/assets/icons/response-time-icon.svg b/src/assets/icons/response-time-icon.svg similarity index 100% rename from Client/src/assets/icons/response-time-icon.svg rename to src/assets/icons/response-time-icon.svg diff --git a/Client/src/assets/icons/resume-icon.svg b/src/assets/icons/resume-icon.svg similarity index 100% rename from Client/src/assets/icons/resume-icon.svg rename to src/assets/icons/resume-icon.svg diff --git a/Client/src/assets/icons/right-arrow-double.svg b/src/assets/icons/right-arrow-double.svg similarity index 100% rename from Client/src/assets/icons/right-arrow-double.svg rename to src/assets/icons/right-arrow-double.svg diff --git a/Client/src/assets/icons/right-arrow.svg b/src/assets/icons/right-arrow.svg similarity index 100% rename from Client/src/assets/icons/right-arrow.svg rename to src/assets/icons/right-arrow.svg diff --git a/Client/src/assets/icons/ruler-icon.svg b/src/assets/icons/ruler-icon.svg similarity index 100% rename from Client/src/assets/icons/ruler-icon.svg rename to src/assets/icons/ruler-icon.svg diff --git a/Client/src/assets/icons/search.svg b/src/assets/icons/search.svg similarity index 100% rename from Client/src/assets/icons/search.svg rename to src/assets/icons/search.svg diff --git a/Client/src/assets/icons/selector-vertical.svg b/src/assets/icons/selector-vertical.svg similarity index 100% rename from Client/src/assets/icons/selector-vertical.svg rename to src/assets/icons/selector-vertical.svg diff --git a/Client/src/assets/icons/settings-bold.svg b/src/assets/icons/settings-bold.svg similarity index 100% rename from Client/src/assets/icons/settings-bold.svg rename to src/assets/icons/settings-bold.svg diff --git a/Client/src/assets/icons/settings.svg b/src/assets/icons/settings.svg similarity index 100% rename from Client/src/assets/icons/settings.svg rename to src/assets/icons/settings.svg diff --git a/Client/src/assets/icons/slack-icon.svg b/src/assets/icons/slack-icon.svg similarity index 100% rename from Client/src/assets/icons/slack-icon.svg rename to src/assets/icons/slack-icon.svg diff --git a/Client/src/assets/icons/solana_logo.svg b/src/assets/icons/solana_logo.svg similarity index 100% rename from Client/src/assets/icons/solana_logo.svg rename to src/assets/icons/solana_logo.svg diff --git a/Client/src/assets/icons/speedometer-icon.svg b/src/assets/icons/speedometer-icon.svg similarity index 100% rename from Client/src/assets/icons/speedometer-icon.svg rename to src/assets/icons/speedometer-icon.svg diff --git a/Client/src/assets/icons/status-pages.svg b/src/assets/icons/status-pages.svg similarity index 100% rename from Client/src/assets/icons/status-pages.svg rename to src/assets/icons/status-pages.svg diff --git a/Client/src/assets/icons/support.svg b/src/assets/icons/support.svg similarity index 100% rename from Client/src/assets/icons/support.svg rename to src/assets/icons/support.svg diff --git a/Client/src/assets/icons/top-right-arrow.svg b/src/assets/icons/top-right-arrow.svg similarity index 100% rename from Client/src/assets/icons/top-right-arrow.svg rename to src/assets/icons/top-right-arrow.svg diff --git a/Client/src/assets/icons/trash-bin.svg b/src/assets/icons/trash-bin.svg similarity index 100% rename from Client/src/assets/icons/trash-bin.svg rename to src/assets/icons/trash-bin.svg diff --git a/Client/src/assets/icons/up-arrow.svg b/src/assets/icons/up-arrow.svg similarity index 100% rename from Client/src/assets/icons/up-arrow.svg rename to src/assets/icons/up-arrow.svg diff --git a/Client/src/assets/icons/upt_logo.png b/src/assets/icons/upt_logo.png similarity index 100% rename from Client/src/assets/icons/upt_logo.png rename to src/assets/icons/upt_logo.png diff --git a/Client/src/assets/icons/uptime-icon.svg b/src/assets/icons/uptime-icon.svg similarity index 100% rename from Client/src/assets/icons/uptime-icon.svg rename to src/assets/icons/uptime-icon.svg diff --git a/Client/src/assets/icons/user-edit.svg b/src/assets/icons/user-edit.svg similarity index 100% rename from Client/src/assets/icons/user-edit.svg rename to src/assets/icons/user-edit.svg diff --git a/Client/src/assets/icons/user-two.svg b/src/assets/icons/user-two.svg similarity index 100% rename from Client/src/assets/icons/user-two.svg rename to src/assets/icons/user-two.svg diff --git a/Client/src/assets/icons/user.svg b/src/assets/icons/user.svg similarity index 100% rename from Client/src/assets/icons/user.svg rename to src/assets/icons/user.svg diff --git a/Client/src/assets/icons/zapier-icon.svg b/src/assets/icons/zapier-icon.svg similarity index 100% rename from Client/src/assets/icons/zapier-icon.svg rename to src/assets/icons/zapier-icon.svg diff --git a/Client/src/index.css b/src/index.css similarity index 100% rename from Client/src/index.css rename to src/index.css diff --git a/Client/src/locales/gb.json b/src/locales/gb.json similarity index 100% rename from Client/src/locales/gb.json rename to src/locales/gb.json diff --git a/Client/src/locales/tr.json b/src/locales/tr.json similarity index 100% rename from Client/src/locales/tr.json rename to src/locales/tr.json diff --git a/Client/src/main.jsx b/src/main.jsx similarity index 100% rename from Client/src/main.jsx rename to src/main.jsx diff --git a/Client/src/store.js b/src/store.js similarity index 100% rename from Client/src/store.js rename to src/store.js diff --git a/Client/vite.config.js b/vite.config.js similarity index 100% rename from Client/vite.config.js rename to vite.config.js