From e090d3cfd3e78ebac3a6cc61bb58595330650b63 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 12 Jan 2026 19:32:23 +0000 Subject: [PATCH] -> timeseries --- server/package-lock.json | 97 ++++ server/package.json | 1 + server/src/app.js | 1 - server/src/config/controllers.js | 47 -- server/src/config/controllers.ts | 35 ++ server/src/config/{routes.js => routes.ts} | 6 +- server/src/config/services.js | 84 +-- server/src/controllers/authController.ts | 233 +++++++++ .../controllers/{v1 => }/checkController.ts | 0 .../controllers/{v1 => }/controllerUtils.ts | 0 .../{v1 => }/diagnosticController.ts | 0 .../{v1 => }/incidentController.ts | 0 .../controllers/{v1 => }/inviteController.ts | 0 .../src/controllers/{v1 => }/logController.ts | 0 .../{v1 => }/maintenanceWindowController.ts | 0 .../controllers/{v1 => }/monitorController.ts | 2 +- .../{v1 => }/notificationController.ts | 0 .../controllers/{v1 => }/queueController.ts | 0 .../{v1 => }/settingsController.ts | 0 .../{v1 => }/statusPageController.ts | 0 .../controllers/v1/announcementsController.js | 77 --- server/src/controllers/v1/authController.js | 478 ------------------ server/src/controllers/v1/baseController.js | 83 --- server/src/db/{v1 => }/MongoDB.js | 0 .../0001_migrateStatusWindowThreshold.ts | 10 + .../0002_convertChecksToTimeSeries.ts | 93 ++++ server/src/db/migration/index.ts | 51 ++ server/src/db/{v1 => }/models/AppSettings.js | 0 server/src/db/models/Check.ts | 214 ++++++++ server/src/db/{v1 => }/models/Incident.js | 0 server/src/db/{v1 => }/models/InviteToken.js | 0 .../db/{v1 => }/models/MaintenanceWindow.js | 0 server/src/db/models/Migration.ts | 13 + server/src/db/{v1 => }/models/Monitor.js | 0 server/src/db/{v1 => }/models/MonitorStats.js | 0 server/src/db/{v1 => }/models/Notification.js | 0 .../src/db/{v1 => }/models/RecoveryToken.js | 0 server/src/db/{v1 => }/models/StatusPage.js | 0 server/src/db/{v1 => }/models/Team.js | 0 server/src/db/{v1 => }/models/User.js | 0 server/src/db/{v1 => }/modules/checkModule.js | 0 .../db/{v1 => }/modules/checkModuleQueries.js | 0 .../src/db/{v1 => }/modules/incidentModule.js | 0 .../src/db/{v1 => }/modules/inviteModule.js | 0 .../modules/maintenanceWindowModule.js | 0 .../src/db/{v1 => }/modules/monitorModule.js | 0 .../{v1 => }/modules/monitorModuleQueries.js | 0 .../db/{v1 => }/modules/notificationModule.js | 0 .../src/db/{v1 => }/modules/recoveryModule.js | 0 .../src/db/{v1 => }/modules/settingsModule.js | 0 .../db/{v1 => }/modules/statusPageModule.js | 0 server/src/db/{v1 => }/modules/userModule.js | 0 server/src/db/{v1 => }/utils/seedDb.js | 0 .../0001_migrateStatusWindowThreshold.ts | 18 - server/src/db/v1/migration/index.ts | 7 - server/src/db/v1/models/Check.js | 178 ------- server/src/index.js | 7 +- server/src/middleware/v1/handleErrors.js | 4 +- server/src/middleware/v1/isAllowed.js | 6 +- server/src/middleware/v1/verifyJWT.js | 6 +- server/src/middleware/v1/verifyOwnership.js | 4 +- .../repositories/checks/IChecksRepository.ts | 0 .../checks/MongoChecksRepistory.ts | 0 .../monitors/IMonitorsRepository.ts | 9 + .../monitors/MongoMonitorsRepository.ts | 5 + server/src/routes/v1/announcementsRoute.js | 33 -- server/src/routes/v1/monitorRoute.js | 2 +- .../service/{v1 => }/business/checkService.js | 0 .../{v1 => }/business/diagnosticService.js | 0 .../{v1 => }/business/incidentService.js | 0 .../{v1 => }/business/inviteService.js | 0 .../business/maintenanceWindowService.js | 0 .../{v1 => }/business/monitorService.js | 2 +- .../service/{v1 => }/business/userService.js | 0 .../SuperSimpleQueue/SuperSimpleQueue.js | 0 .../SuperSimpleQueueHelper.js | 0 .../{v1 => }/infrastructure/bufferService.js | 0 .../{v1 => }/infrastructure/emailService.js | 0 .../{v1 => }/infrastructure/errorService.js | 0 .../{v1 => }/infrastructure/networkService.js | 0 .../notificationProviders/matrix.js | 0 .../infrastructure/notificationService.js | 0 .../infrastructure/notificationUtils.js | 0 .../{v1 => }/infrastructure/statusService.js | 12 +- .../{v1 => }/system/serviceRegistry.js | 0 .../{v1 => }/system/settingsService.js | 0 .../service/{v1 => }/system/stringService.js | 0 .../{v1 => }/system/translationService.js | 0 server/src/types/check.ts | 79 +++ server/src/types/index.ts | 2 + server/src/types/monitor.ts | 29 ++ 91 files changed, 941 insertions(+), 987 deletions(-) delete mode 100644 server/src/config/controllers.js create mode 100644 server/src/config/controllers.ts rename server/src/config/{routes.js => routes.ts} (92%) create mode 100755 server/src/controllers/authController.ts rename server/src/controllers/{v1 => }/checkController.ts (100%) rename server/src/controllers/{v1 => }/controllerUtils.ts (100%) rename server/src/controllers/{v1 => }/diagnosticController.ts (100%) rename server/src/controllers/{v1 => }/incidentController.ts (100%) rename server/src/controllers/{v1 => }/inviteController.ts (100%) rename server/src/controllers/{v1 => }/logController.ts (100%) rename server/src/controllers/{v1 => }/maintenanceWindowController.ts (100%) rename server/src/controllers/{v1 => }/monitorController.ts (99%) rename server/src/controllers/{v1 => }/notificationController.ts (100%) rename server/src/controllers/{v1 => }/queueController.ts (100%) rename server/src/controllers/{v1 => }/settingsController.ts (100%) rename server/src/controllers/{v1 => }/statusPageController.ts (100%) delete mode 100755 server/src/controllers/v1/announcementsController.js delete mode 100755 server/src/controllers/v1/authController.js delete mode 100644 server/src/controllers/v1/baseController.js rename server/src/db/{v1 => }/MongoDB.js (100%) create mode 100644 server/src/db/migration/0001_migrateStatusWindowThreshold.ts create mode 100644 server/src/db/migration/0002_convertChecksToTimeSeries.ts create mode 100644 server/src/db/migration/index.ts rename server/src/db/{v1 => }/models/AppSettings.js (100%) create mode 100755 server/src/db/models/Check.ts rename server/src/db/{v1 => }/models/Incident.js (100%) rename server/src/db/{v1 => }/models/InviteToken.js (100%) rename server/src/db/{v1 => }/models/MaintenanceWindow.js (100%) create mode 100644 server/src/db/models/Migration.ts rename server/src/db/{v1 => }/models/Monitor.js (100%) rename server/src/db/{v1 => }/models/MonitorStats.js (100%) rename server/src/db/{v1 => }/models/Notification.js (100%) rename server/src/db/{v1 => }/models/RecoveryToken.js (100%) rename server/src/db/{v1 => }/models/StatusPage.js (100%) rename server/src/db/{v1 => }/models/Team.js (100%) rename server/src/db/{v1 => }/models/User.js (100%) rename server/src/db/{v1 => }/modules/checkModule.js (100%) rename server/src/db/{v1 => }/modules/checkModuleQueries.js (100%) rename server/src/db/{v1 => }/modules/incidentModule.js (100%) rename server/src/db/{v1 => }/modules/inviteModule.js (100%) rename server/src/db/{v1 => }/modules/maintenanceWindowModule.js (100%) rename server/src/db/{v1 => }/modules/monitorModule.js (100%) rename server/src/db/{v1 => }/modules/monitorModuleQueries.js (100%) rename server/src/db/{v1 => }/modules/notificationModule.js (100%) rename server/src/db/{v1 => }/modules/recoveryModule.js (100%) rename server/src/db/{v1 => }/modules/settingsModule.js (100%) rename server/src/db/{v1 => }/modules/statusPageModule.js (100%) rename server/src/db/{v1 => }/modules/userModule.js (100%) rename server/src/db/{v1 => }/utils/seedDb.js (100%) delete mode 100644 server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts delete mode 100644 server/src/db/v1/migration/index.ts delete mode 100755 server/src/db/v1/models/Check.js create mode 100644 server/src/repositories/checks/IChecksRepository.ts create mode 100644 server/src/repositories/checks/MongoChecksRepistory.ts create mode 100644 server/src/repositories/monitors/IMonitorsRepository.ts create mode 100644 server/src/repositories/monitors/MongoMonitorsRepository.ts delete mode 100755 server/src/routes/v1/announcementsRoute.js rename server/src/service/{v1 => }/business/checkService.js (100%) rename server/src/service/{v1 => }/business/diagnosticService.js (100%) rename server/src/service/{v1 => }/business/incidentService.js (100%) rename server/src/service/{v1 => }/business/inviteService.js (100%) rename server/src/service/{v1 => }/business/maintenanceWindowService.js (100%) rename server/src/service/{v1 => }/business/monitorService.js (99%) rename server/src/service/{v1 => }/business/userService.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js (100%) rename server/src/service/{v1 => }/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js (100%) rename server/src/service/{v1 => }/infrastructure/bufferService.js (100%) rename server/src/service/{v1 => }/infrastructure/emailService.js (100%) rename server/src/service/{v1 => }/infrastructure/errorService.js (100%) rename server/src/service/{v1 => }/infrastructure/networkService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationProviders/matrix.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationService.js (100%) rename server/src/service/{v1 => }/infrastructure/notificationUtils.js (100%) rename server/src/service/{v1 => }/infrastructure/statusService.js (98%) rename server/src/service/{v1 => }/system/serviceRegistry.js (100%) rename server/src/service/{v1 => }/system/settingsService.js (100%) rename server/src/service/{v1 => }/system/stringService.js (100%) rename server/src/service/{v1 => }/system/translationService.js (100%) create mode 100644 server/src/types/check.ts create mode 100644 server/src/types/index.ts create mode 100644 server/src/types/monitor.ts diff --git a/server/package-lock.json b/server/package-lock.json index e08248989..eaf771a61 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -57,6 +57,7 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", "sinon": "19.0.2", "tsc-alias": "1.8.16", @@ -6732,6 +6733,13 @@ "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.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8745,6 +8753,58 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "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/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/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/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9797,6 +9857,13 @@ "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.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -10424,6 +10491,19 @@ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "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", @@ -11046,6 +11126,16 @@ "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/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -11225,6 +11315,13 @@ "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.22.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", diff --git a/server/package.json b/server/package.json index 050dd8e66..5610d32a9 100755 --- a/server/package.json +++ b/server/package.json @@ -69,6 +69,7 @@ "esm": "3.2.25", "globals": "^15.14.0", "mocha": "11.1.0", + "nodemon": "^3.1.11", "prettier": "^3.3.3", "sinon": "19.0.2", "tsc-alias": "1.8.16", diff --git a/server/src/app.js b/server/src/app.js index 4578de896..d782d07eb 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -14,7 +14,6 @@ import { sanitizeBody, sanitizeQuery } from "./middleware/v1/sanitization.js"; export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { const allowedOrigin = envSettings.clientHost; - const app = express(); app.use(generalApiLimiter); // Static files diff --git a/server/src/config/controllers.js b/server/src/config/controllers.js deleted file mode 100644 index afd558930..000000000 --- a/server/src/config/controllers.js +++ /dev/null @@ -1,47 +0,0 @@ -import { createCommonDependencies } from "../controllers/v1/baseController.js"; - -// Services - -// V1 Controllers -import MonitorController from "../controllers/v1/monitorController.js"; -import AuthController from "../controllers/v1/authController.js"; -import SettingsController from "../controllers/v1/settingsController.js"; -import CheckController from "../controllers/v1/checkController.js"; -import InviteController from "../controllers/v1/inviteController.js"; -import MaintenanceWindowController from "../controllers/v1/maintenanceWindowController.js"; -import QueueController from "../controllers/v1/queueController.js"; -import LogController from "../controllers/v1/logController.js"; -import StatusPageController from "../controllers/v1/statusPageController.js"; -import NotificationController from "../controllers/v1/notificationController.js"; -import DiagnosticController from "../controllers/v1/diagnosticController.js"; -import IncidentController from "../controllers/v1/incidentController.js"; - -export const initializeControllers = (services) => { - const controllers = {}; - const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService); - - // V1 - controllers.authController = new AuthController(commonDependencies, { - settingsService: services.settingsService, - emailService: services.emailService, - jobQueue: services.jobQueue, - userService: services.userService, - }); - - controllers.monitorController = new MonitorController(services.monitorService); - - controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); - controllers.checkController = new CheckController(services.checkService); - controllers.inviteController = new InviteController(services.inviteService); - - controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); - controllers.queueController = new QueueController(services.jobQueue); - controllers.logController = new LogController(services.logger); - controllers.statusPageController = new StatusPageController(services.db); - controllers.notificationController = new NotificationController(services.notificationService, services.db); - controllers.diagnosticController = new DiagnosticController(services.diagnosticService); - - controllers.incidentController = new IncidentController(services.incidentService); - - return controllers; -}; diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts new file mode 100644 index 000000000..9fe834348 --- /dev/null +++ b/server/src/config/controllers.ts @@ -0,0 +1,35 @@ +import MonitorController from "../controllers/monitorController.js"; +import AuthController from "../controllers/authController.js"; +import SettingsController from "../controllers/settingsController.js"; +import CheckController from "../controllers/checkController.js"; +import InviteController from "../controllers/inviteController.js"; +import MaintenanceWindowController from "../controllers/maintenanceWindowController.js"; +import QueueController from "../controllers/queueController.js"; +import LogController from "../controllers/logController.js"; +import StatusPageController from "../controllers/statusPageController.js"; +import NotificationController from "../controllers/notificationController.js"; +import DiagnosticController from "../controllers/diagnosticController.js"; +import IncidentController from "../controllers/incidentController.js"; + +export const initializeControllers = (services: any) => { + const controllers: Record = {}; + + controllers.authController = new AuthController(services.userService); + + controllers.monitorController = new MonitorController(services.monitorService); + + controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); + controllers.checkController = new CheckController(services.checkService); + controllers.inviteController = new InviteController(services.inviteService); + + controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); + controllers.queueController = new QueueController(services.jobQueue); + controllers.logController = new LogController(services.logger); + controllers.statusPageController = new StatusPageController(services.db); + controllers.notificationController = new NotificationController(services.notificationService, services.db); + controllers.diagnosticController = new DiagnosticController(services.diagnosticService); + + controllers.incidentController = new IncidentController(services.incidentService); + + return controllers; +}; diff --git a/server/src/config/routes.js b/server/src/config/routes.ts similarity index 92% rename from server/src/config/routes.js rename to server/src/config/routes.ts index 97b2d6a1d..303dd4eb7 100644 --- a/server/src/config/routes.js +++ b/server/src/config/routes.ts @@ -2,7 +2,7 @@ import { verifyJWT } from "../middleware/v1/verifyJWT.js"; import { authApiLimiter } from "../middleware/v1/rateLimiter.js"; import AuthRoutes from "../routes/v1/authRoute.js"; -import InviteRoutes from "../routes/v1//inviteRoute.js"; +import InviteRoutes from "../routes/v1/inviteRoute.js"; import MonitorRoutes from "../routes/v1/monitorRoute.js"; import CheckRoutes from "../routes/v1/checkRoute.js"; import SettingsRoutes from "../routes/v1/settingsRoute.js"; @@ -10,12 +10,12 @@ import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js"; import StatusPageRoutes from "../routes/v1/statusPageRoute.js"; import QueueRoutes from "../routes/v1/queueRoute.js"; import LogRoutes from "../routes/v1/logRoutes.js"; -import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js"; +import DiagnosticRoutes from "../routes/v1/diagnosticRoute.js"; import NotificationRoutes from "../routes/v1/notificationRoute.js"; import IncidentRoutes from "../routes/v1/incidentRoute.js"; -export const setupRoutes = (app, controllers) => { +export const setupRoutes = (app: any, controllers: Record) => { // V1 const authRoutes = new AuthRoutes(controllers.authController); const monitorRoutes = new MonitorRoutes(controllers.monitorController); diff --git a/server/src/config/services.js b/server/src/config/services.js index 9744c647d..28b7c9a86 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -1,23 +1,23 @@ -import ServiceRegistry from "../service/v1/system/serviceRegistry.js"; -import TranslationService from "../service/v1/system/translationService.js"; -import StringService from "../service/v1/system/stringService.js"; -import MongoDB from "../db/v1/MongoDB.js"; -import NetworkService from "../service/v1/infrastructure/networkService.js"; -import EmailService from "../service/v1/infrastructure/emailService.js"; -import BufferService from "../service/v1/infrastructure/bufferService.js"; -import StatusService from "../service/v1/infrastructure/statusService.js"; -import NotificationUtils from "../service/v1/infrastructure/notificationUtils.js"; -import NotificationService from "../service/v1/infrastructure/notificationService.js"; -import ErrorService from "../service/v1/infrastructure/errorService.js"; -import SuperSimpleQueueHelper from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import SuperSimpleQueue from "../service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; -import UserService from "../service/v1/business/userService.js"; -import CheckService from "../service/v1/business/checkService.js"; -import DiagnosticService from "../service/v1/business/diagnosticService.js"; -import InviteService from "../service/v1/business/inviteService.js"; -import MaintenanceWindowService from "../service/v1/business/maintenanceWindowService.js"; -import MonitorService from "../service/v1/business/monitorService.js"; -import IncidentService from "../service/v1/business/incidentService.js"; +import ServiceRegistry from "../service/system/serviceRegistry.js"; +import TranslationService from "../service/system/translationService.js"; +import StringService from "../service/system/stringService.js"; +import MongoDB from "../db/MongoDB.js"; +import NetworkService from "../service/infrastructure/networkService.js"; +import EmailService from "../service/infrastructure/emailService.js"; +import BufferService from "../service/infrastructure/bufferService.js"; +import StatusService from "../service/infrastructure/statusService.js"; +import NotificationUtils from "../service/infrastructure/notificationUtils.js"; +import NotificationService from "../service/infrastructure/notificationService.js"; +import ErrorService from "../service/infrastructure/errorService.js"; +import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import UserService from "../service/business/userService.js"; +import CheckService from "../service/business/checkService.js"; +import DiagnosticService from "../service/business/diagnosticService.js"; +import InviteService from "../service/business/inviteService.js"; +import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; +import MonitorService from "../service/business/monitorService.js"; +import IncidentService from "../service/business/incidentService.js"; import papaparse from "papaparse"; import axios from "axios"; import got from "got"; @@ -47,29 +47,29 @@ import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; // Models -import Check from "../db/v1/models/Check.js"; -import Monitor from "../db/v1/models/Monitor.js"; -import User from "../db/v1/models/User.js"; -import InviteToken from "../db/v1/models/InviteToken.js"; -import StatusPage from "../db/v1/models/StatusPage.js"; -import Team from "../db/v1/models/Team.js"; -import MaintenanceWindow from "../db/v1/models/MaintenanceWindow.js"; -import MonitorStats from "../db/v1/models/MonitorStats.js"; -import Notification from "../db/v1/models/Notification.js"; -import RecoveryToken from "../db/v1/models/RecoveryToken.js"; -import AppSettings from "../db/v1/models/AppSettings.js"; -import Incident from "../db/v1/models/Incident.js"; +import Check from "../db/models/Check.js"; +import Monitor from "../db/models/Monitor.js"; +import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; +import Team from "../db/models/Team.js"; +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MonitorStats from "../db/models/MonitorStats.js"; +import Notification from "../db/models/Notification.js"; +import RecoveryToken from "../db/models/RecoveryToken.js"; +import AppSettings from "../db/models/AppSettings.js"; +import Incident from "../db/models/Incident.js"; -import InviteModule from "../db/v1/modules/inviteModule.js"; -import CheckModule from "../db/v1/modules/checkModule.js"; -import StatusPageModule from "../db/v1/modules/statusPageModule.js"; -import UserModule from "../db/v1/modules/userModule.js"; -import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js"; -import MonitorModule from "../db/v1/modules/monitorModule.js"; -import NotificationModule from "../db/v1/modules/notificationModule.js"; -import RecoveryModule from "../db/v1/modules/recoveryModule.js"; -import SettingsModule from "../db/v1/modules/settingsModule.js"; -import IncidentModule from "../db/v1/modules/incidentModule.js"; +import InviteModule from "../db/modules/inviteModule.js"; +import CheckModule from "../db/modules/checkModule.js"; +import StatusPageModule from "../db/modules/statusPageModule.js"; +import UserModule from "../db/modules/userModule.js"; +import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js"; +import MonitorModule from "../db/modules/monitorModule.js"; +import NotificationModule from "../db/modules/notificationModule.js"; +import RecoveryModule from "../db/modules/recoveryModule.js"; +import SettingsModule from "../db/modules/settingsModule.js"; +import IncidentModule from "../db/modules/incidentModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts new file mode 100755 index 000000000..d033aa39d --- /dev/null +++ b/server/src/controllers/authController.ts @@ -0,0 +1,233 @@ +import { Request, Response, NextFunction } from "express"; +import { AppError } from "@/utils/AppError.js"; + +import { + registrationBodyValidation, + loginValidation, + editUserBodyValidation, + recoveryValidation, + recoveryTokenBodyValidation, + newPasswordValidation, + getUserByIdParamValidation, + editUserByIdParamValidation, + editUserByIdBodyValidation, + editSuperadminUserByIdBodyValidation, + editUserPasswordByIdBodyValidation, +} from "@/validation/joi.js"; + +const SERVICE_NAME = "authController"; + +class AuthController { + static SERVICE_NAME = SERVICE_NAME; + + private userService: any; + + constructor(userService: any) { + this.userService = userService; + } + + get serviceName() { + return AuthController.SERVICE_NAME; + } + + registerUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await registrationBodyValidation.validateAsync(req.body); + const { user, token } = await this.userService.registerUser(req.body, req.file); + res.status(200).json({ + success: true, + msg: "User registered successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + loginUser = async (req: Request, res: Response, next: NextFunction) => { + try { + if (req.body?.email) { + req.body.email = req.body.email?.toLowerCase(); + } + await loginValidation.validateAsync(req.body); + const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); + + return res.status(200).json({ + success: true, + msg: "User logged in successfully", + data: { + user, + token, + }, + }); + } catch (error) { + next(error); + } + }; + + editUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await editUserBodyValidation.validateAsync(req.body); + + const updatedUser = await this.userService.editUser(req.body, req.file, req.user); + + res.status(200).json({ + success: true, + msg: "User updated successfully", + data: updatedUser, + }); + } catch (error) { + next(error); + } + }; + + checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => { + try { + const superAdminExists = await this.userService.checkSuperadminExists(); + return res.status(200).json({ + success: true, + msg: "Superadmin existence checked successfully", + data: superAdminExists, + }); + } catch (error) { + next(error); + } + }; + + requestRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryValidation.validateAsync(req.body); + const email = req?.body?.email; + const msgId = await this.userService.requestRecovery(email); + return res.status(200).json({ + success: true, + msg: "Password recovery email sent successfully", + data: msgId, + }); + } catch (error) { + next(error); + } + }; + + validateRecovery = async (req: Request, res: Response, next: NextFunction) => { + try { + await recoveryTokenBodyValidation.validateAsync(req.body); + await this.userService.validateRecovery(req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Recovery token is valid", + }); + } catch (error) { + next(error); + } + }; + + resetPassword = async (req: Request, res: Response, next: NextFunction) => { + try { + await newPasswordValidation.validateAsync(req.body); + const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); + return res.status(200).json({ + success: true, + msg: "Password has been reset successfully", + data: { user, token }, + }); + } catch (error) { + next(error); + } + }; + + deleteUser = async (req: Request, res: Response, next: NextFunction) => { + try { + await this.userService.deleteUser(req.user); + return res.status(200).json({ + success: true, + msg: "User deleted successfully", + }); + } catch (error) { + next(error); + } + }; + + getAllUsers = async (req: Request, res: Response, next: NextFunction) => { + try { + const allUsers = await this.userService.getAllUsers(); + return res.status(200).json({ + success: true, + msg: "Users retrieved successfully", + data: allUsers, + }); + } catch (error) { + next(error); + } + }; + + getUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + await getUserByIdParamValidation.validateAsync(req.params); + const userId = req?.params?.userId; + const roles = req?.user?.role; + + if (!userId) { + throw new Error("No user ID in request"); + } + + if (!roles || roles.length === 0) { + throw new Error("No roles in request"); + } + + const user = await this.userService.getUserById(roles, userId); + + return res.status(200).json({ success: true, msg: "ok", data: user }); + } catch (error) { + next(error); + } + }; + + editUserById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + const user = { ...req.body }; + + await editUserByIdParamValidation.validateAsync(req.params); + // If this is superadmin self edit, allow "superadmin" role + if (userId === req.user._id) { + await editSuperadminUserByIdBodyValidation.validateAsync(req.body); + } else { + await editUserByIdBodyValidation.validateAsync(req.body); + } + + await this.userService.editUserById(userId, user); + return res.status(200).json({ success: true, msg: "ok" }); + } catch (error) { + next(error); + } + }; + + editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => { + try { + const roles = req?.user?.role; + if (!roles.includes("superadmin")) { + throw new AppError({ message: "Unauthorized", status: 403 }); + } + + const userId = req.params.userId; + await editUserByIdParamValidation.validateAsync(req.params); + await editUserPasswordByIdBodyValidation.validateAsync(req.body); + const updatedPassword = req.body.password; + await this.userService.setPasswordByUserId(userId, updatedPassword); + return res.status(200).json({ success: true, msg: "Password reset successfully" }); + } catch (error) { + next(error); + } + }; +} + +export default AuthController; diff --git a/server/src/controllers/v1/checkController.ts b/server/src/controllers/checkController.ts similarity index 100% rename from server/src/controllers/v1/checkController.ts rename to server/src/controllers/checkController.ts diff --git a/server/src/controllers/v1/controllerUtils.ts b/server/src/controllers/controllerUtils.ts similarity index 100% rename from server/src/controllers/v1/controllerUtils.ts rename to server/src/controllers/controllerUtils.ts diff --git a/server/src/controllers/v1/diagnosticController.ts b/server/src/controllers/diagnosticController.ts similarity index 100% rename from server/src/controllers/v1/diagnosticController.ts rename to server/src/controllers/diagnosticController.ts diff --git a/server/src/controllers/v1/incidentController.ts b/server/src/controllers/incidentController.ts similarity index 100% rename from server/src/controllers/v1/incidentController.ts rename to server/src/controllers/incidentController.ts diff --git a/server/src/controllers/v1/inviteController.ts b/server/src/controllers/inviteController.ts similarity index 100% rename from server/src/controllers/v1/inviteController.ts rename to server/src/controllers/inviteController.ts diff --git a/server/src/controllers/v1/logController.ts b/server/src/controllers/logController.ts similarity index 100% rename from server/src/controllers/v1/logController.ts rename to server/src/controllers/logController.ts diff --git a/server/src/controllers/v1/maintenanceWindowController.ts b/server/src/controllers/maintenanceWindowController.ts similarity index 100% rename from server/src/controllers/v1/maintenanceWindowController.ts rename to server/src/controllers/maintenanceWindowController.ts diff --git a/server/src/controllers/v1/monitorController.ts b/server/src/controllers/monitorController.ts similarity index 99% rename from server/src/controllers/v1/monitorController.ts rename to server/src/controllers/monitorController.ts index aac59cd27..425979062 100644 --- a/server/src/controllers/v1/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -13,7 +13,7 @@ import { getCertificateParamValidation, getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, -} from "../../validation/joi.js"; +} from "@/validation/joi.js"; import sslChecker from "ssl-checker"; import { fetchMonitorCertificate } from "./controllerUtils.js"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/controllers/v1/notificationController.ts b/server/src/controllers/notificationController.ts similarity index 100% rename from server/src/controllers/v1/notificationController.ts rename to server/src/controllers/notificationController.ts diff --git a/server/src/controllers/v1/queueController.ts b/server/src/controllers/queueController.ts similarity index 100% rename from server/src/controllers/v1/queueController.ts rename to server/src/controllers/queueController.ts diff --git a/server/src/controllers/v1/settingsController.ts b/server/src/controllers/settingsController.ts similarity index 100% rename from server/src/controllers/v1/settingsController.ts rename to server/src/controllers/settingsController.ts diff --git a/server/src/controllers/v1/statusPageController.ts b/server/src/controllers/statusPageController.ts similarity index 100% rename from server/src/controllers/v1/statusPageController.ts rename to server/src/controllers/statusPageController.ts diff --git a/server/src/controllers/v1/announcementsController.js b/server/src/controllers/v1/announcementsController.js deleted file mode 100755 index 0425b44aa..000000000 --- a/server/src/controllers/v1/announcementsController.js +++ /dev/null @@ -1,77 +0,0 @@ -import { createAnnouncementValidation } from "../../validation/joi.js"; -import BaseController from "./baseController.js"; - -const SERVICE_NAME = "announcementController"; - -/** - * Controller for managing announcements in the system. - * This class handles the creation of new announcements. - * - * @class AnnouncementController - */ - -class AnnouncementController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - constructor(commonDependencies) { - super(commonDependencies); - this.createAnnouncement = this.createAnnouncement.bind(this); - this.getAnnouncement = this.getAnnouncement.bind(this); - } - - get serviceName() { - return AnnouncementController.SERVICE_NAME; - } - - /** - * Handles the creation of a new announcement. - * - * @async - * @param {Object} req - The request object, containing the announcement data in the body. - * @param {Object} res - The response object used to send the result back to the client. - * @param {Function} next - The next middleware function in the stack for error handling. - * - * @returns {Promise} A promise that resolves once the response is sent. - */ - createAnnouncement = asyncHandler( - async (req, res, next) => { - await createAnnouncementValidation.validateAsync(req.body); - const { title, message } = req.body; - const announcementData = { - title: title.trim(), - message: message.trim(), - userId: req.user._id, - }; - - const newAnnouncement = await this.db.createAnnouncement(announcementData); - return res.success({ - msg: this.stringService.createAnnouncement, - data: newAnnouncement, - }); - }, - SERVICE_NAME, - "createAnnouncement" - ); - - /** - * Handles retrieving announcements with pagination. - * - * @async - * @param {Object} res - The response object used to send the result back to the client. - * - `data`: The list of announcements to be sent back to the client. - * - `msg`: A message about the success of the request. - * @param {Function} next - The next middleware function in the stack for error handling. - */ - getAnnouncement = asyncHandler( - async (req, res, next) => { - const allAnnouncements = await this.db.getAnnouncements(); - return res.success({ - msg: this.stringService.getAnnouncement, - data: allAnnouncements, - }); - }, - SERVICE_NAME, - "getAnnouncement" - ); -} - -export default AnnouncementController; diff --git a/server/src/controllers/v1/authController.js b/server/src/controllers/v1/authController.js deleted file mode 100755 index e52de3849..000000000 --- a/server/src/controllers/v1/authController.js +++ /dev/null @@ -1,478 +0,0 @@ -import BaseController from "./baseController.js"; -import { - registrationBodyValidation, - loginValidation, - editUserBodyValidation, - recoveryValidation, - recoveryTokenBodyValidation, - newPasswordValidation, - getUserByIdParamValidation, - editUserByIdParamValidation, - editUserByIdBodyValidation, - editSuperadminUserByIdBodyValidation, - editUserPasswordByIdBodyValidation, -} from "../../validation/joi.js"; - -const SERVICE_NAME = "authController"; -/** - * Authentication Controller - * - * Handles all authentication-related HTTP requests including user registration, - * login, password recovery, and user management operations. - * - * @class AuthController - * @description Manages user authentication and authorization operations - */ -class AuthController extends BaseController { - static SERVICE_NAME = SERVICE_NAME; - /** - * Creates an instance of AuthController. - * - * @param {Object} commonDependencies - Common dependencies injected into the controller - * @param {Object} dependencies - The dependencies required by the controller - * @param {Object} dependencies.settingsService - Service for application settings - * @param {Object} dependencies.emailService - Service for email operations - * @param {Object} dependencies.jobQueue - Service for job queue operations - * @param {Object} dependencies.userService - User business logic service - */ - constructor(commonDependencies, { settingsService, emailService, jobQueue, userService }) { - super(commonDependencies); - this.settingsService = settingsService; - this.emailService = emailService; - this.jobQueue = jobQueue; - this.userService = userService; - } - - get serviceName() { - return AuthController.SERVICE_NAME; - } - - /** - * Registers a new user in the system. - * - * @async - * @function registerUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user registration data - * @param {string} req.body.firstName - User's first name - * @param {string} req.body.lastName - User's last name - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {string} [req.body.inviteToken] - Invite token for registration (required if superadmin exists) - * @param {string} [req.body.teamId] - Team ID (auto-assigned if superadmin) - * @param {Array} [req.body.role] - User roles (auto-assigned if superadmin) - * @param {Object} [req.file] - Profile image file uploaded via multer - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 409 - Conflict if user already exists - * @example - * // Register first user (becomes superadmin) - * POST /auth/register - * { - * "firstName": "John", - * "lastName": "Doe", - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - * - * // Register subsequent user (requires invite token) - * POST /auth/register - * { - * "firstName": "Jane", - * "lastName": "Smith", - * "email": "jane@example.com", - * "password": "SecurePass123!", - * "inviteToken": "abc123..." - * } - */ - registerUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await registrationBodyValidation.validateAsync(req.body); - const { user, token } = await this.userService.registerUser(req.body, req.file); - res.success({ - msg: this.stringService.authCreateUser, - data: { user, token }, - }); - }, - SERVICE_NAME, - "registerUser" - ); - - /** - * Authenticates a user and returns a JWT token. - * - * @async - * @function loginUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing login credentials - * @param {string} req.body.email - User's email address (will be converted to lowercase) - * @param {string} req.body.password - User's password - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 401 - Unauthorized if credentials are incorrect - * @example - * POST /auth/login - * { - * "email": "john@example.com", - * "password": "SecurePass123!" - * } - */ - loginUser = this.asyncHandler( - async (req, res) => { - if (req.body?.email) { - req.body.email = req.body.email?.toLowerCase(); - } - await loginValidation.validateAsync(req.body); - const { user, token } = await this.userService.loginUser(req.body.email, req.body.password); - - return res.success({ - msg: this.stringService.authLoginUser, - data: { - user, - token, - }, - }); - }, - SERVICE_NAME, - "loginUser" - ); - - /** - * Updates the current user's profile information. - * - * @async - * @function editUser - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {string} [req.body.password] - Current password (required for password change) - * @param {string} [req.body.newPassword] - New password (required for password change) - * @param {boolean} [req.body.deleteProfileImage] - Flag to delete profile image - * @param {Object} [req.file] - New profile image file - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Object} res - Express response object - * @returns {Promise} Success response with updated user data - * @throws {Error} 422 - Validation error if request body is invalid - * @throws {Error} 403 - Forbidden if current password is incorrect - * @example - * PUT /auth/user - * { - * "firstName": "John Updated", - * "lastName": "Doe Updated" - * } - * - * // Change password - * PUT /auth/user - * { - * "password": "OldPass123!", - * "newPassword": "NewPass123!" - * } - */ - editUser = this.asyncHandler( - async (req, res) => { - await editUserBodyValidation.validateAsync(req.body); - - const updatedUser = await this.userService.editUser(req.body, req.file, req.user); - - res.success({ - msg: this.stringService.authUpdateUser, - data: updatedUser, - }); - }, - SERVICE_NAME, - "editUser" - ); - - /** - * Checks if a superadmin account exists in the system. - * - * @async - * @function checkSuperadminExists - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with boolean indicating superadmin existence - * @example - * GET /auth/users/superadmin - * // Response: { "data": true } or { "data": false } - */ - checkSuperadminExists = this.asyncHandler( - async (req, res) => { - const superAdminExists = await this.userService.checkSuperadminExists(); - return res.success({ - msg: this.stringService.authAdminExists, - data: superAdminExists, - }); - }, - SERVICE_NAME, - "checkSuperadminExists" - ); - - /** - * Initiates password recovery process by sending a recovery email. - * - * @async - * @function requestRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing email - * @param {string} req.body.email - Email address for password recovery - * @param {Object} res - Express response object - * @returns {Promise} Success response with message ID - * @throws {Error} 422 - Validation error if email is invalid - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * POST /auth/recovery/request - * { - * "email": "john@example.com" - * } - */ - requestRecovery = this.asyncHandler( - async (req, res) => { - await recoveryValidation.validateAsync(req.body); - const email = req?.body?.email; - const msgId = await this.userService.requestRecovery(email); - return res.success({ - msg: this.stringService.authCreateRecoveryToken, - data: msgId, - }); - }, - SERVICE_NAME, - "requestRecovery" - ); - - /** - * Validates a password recovery token. - * - * @async - * @function validateRecovery - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing recovery token - * @param {string} req.body.recoveryToken - Recovery token to validate - * @param {Object} res - Express response object - * @returns {Promise} Success response if token is valid - * @throws {Error} 422 - Validation error if token format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/validate - * { - * "recoveryToken": "abc123..." - * } - */ - validateRecovery = this.asyncHandler( - async (req, res) => { - await recoveryTokenBodyValidation.validateAsync(req.body); - await this.userService.validateRecovery(req.body.recoveryToken); - return res.success({ - msg: this.stringService.authVerifyRecoveryToken, - }); - }, - SERVICE_NAME, - "validateRecovery" - ); - - /** - * Resets user password using a valid recovery token. - * - * @async - * @function resetPassword - * @param {Object} req - Express request object - * @param {Object} req.body - Request body containing new password and recovery token - * @param {string} req.body.password - New password - * @param {string} req.body.recoveryToken - Valid recovery token - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data and JWT token - * @throws {Error} 422 - Validation error if password format is invalid - * @throws {Error} 400 - Bad request if token is invalid or expired - * @example - * POST /auth/recovery/reset - * { - * "password": "NewSecurePass123!", - * "recoveryToken": "abc123..." - * } - */ - resetPassword = this.asyncHandler( - async (req, res) => { - await newPasswordValidation.validateAsync(req.body); - const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken); - return res.success({ - msg: this.stringService.authResetPassword, - data: { user, token }, - }); - }, - SERVICE_NAME, - "resetPassword" - ); - - /** - * Deletes the current user's account and associated data. - * - * @async - * @function deleteUser - * @param {Object} req - Express request object - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - User ID - * @param {string} req.user.email - User email - * @param {string} req.user.teamId - User's team ID - * @param {Array} req.user.role - User roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user deletion - * @throws {Error} 400 - Bad request if user is demo user - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * DELETE /auth/user - * // Requires JWT authentication - */ - deleteUser = this.asyncHandler( - async (req, res) => { - await this.userService.deleteUser(req.user); - return res.success({ - msg: this.stringService.authDeleteUser, - }); - }, - SERVICE_NAME, - "deleteUser" - ); - - /** - * Retrieves all users in the system (admin/superadmin only). - * - * @async - * @function getAllUsers - * @param {Object} req - Express request object - * @param {Object} res - Express response object - * @returns {Promise} Success response with array of users - * @throws {Error} 403 - Forbidden if user doesn't have admin/superadmin role - * @example - * GET /auth/users - * // Requires JWT authentication with admin/superadmin role - */ - getAllUsers = this.asyncHandler( - async (req, res) => { - const allUsers = await this.userService.getAllUsers(); - return res.success({ - msg: this.stringService.authGetAllUsers, - data: allUsers, - }); - }, - SERVICE_NAME, - "getAllUsers" - ); - - /** - * Retrieves a specific user by ID (superadmin only). - * - * @async - * @function getUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to retrieve - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response with user data - * @throws {Error} 422 - Validation error if userId is invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * GET /auth/users/507f1f77bcf86cd799439011 - * // Requires JWT authentication with superadmin role - */ - getUserById = this.asyncHandler( - async (req, res) => { - await getUserByIdParamValidation.validateAsync(req.params); - const userId = req?.params?.userId; - const roles = req?.user?.role; - - if (!userId) { - throw new Error("No user ID in request"); - } - - if (!roles || roles.length === 0) { - throw new Error("No roles in request"); - } - - const user = await this.userService.getUserById(roles, userId); - - return res.success({ msg: "ok", data: user }); - }, - SERVICE_NAME, - "getUserById" - ); - - /** - * Updates a specific user by ID (superadmin only). - * - * @async - * @function editUserById - * @param {Object} req - Express request object - * @param {Object} req.params - URL parameters - * @param {string} req.params.userId - ID of the user to update - * @param {Object} req.body - Request body containing user update data - * @param {string} [req.body.firstName] - Updated first name - * @param {string} [req.body.lastName] - Updated last name - * @param {Array} [req.body.role] - Updated user roles - * @param {Object} req.user - Current authenticated user (from JWT) - * @param {string} req.user._id - Current user's ID - * @param {Array} req.user.role - Current user's roles - * @param {Object} res - Express response object - * @returns {Promise} Success response confirming user update - * @throws {Error} 422 - Validation error if parameters or body are invalid - * @throws {Error} 403 - Forbidden if user doesn't have superadmin role - * @throws {Error} 404 - Not found if user doesn't exist - * @example - * PUT /auth/users/507f1f77bcf86cd799439011 - * { - * "firstName": "Updated Name", - * "role": ["admin"] - * } - * // Requires JWT authentication with superadmin role - */ - editUserById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - const user = { ...req.body }; - - await editUserByIdParamValidation.validateAsync(req.params); - // If this is superadmin self edit, allow "superadmin" role - if (userId === req.user._id) { - await editSuperadminUserByIdBodyValidation.validateAsync(req.body); - } else { - await editUserByIdBodyValidation.validateAsync(req.body); - } - - await this.userService.editUserById(userId, user); - return res.success({ msg: "ok" }); - }, - SERVICE_NAME, - "editUserById" - ); - editUserPasswordById = this.asyncHandler( - async (req, res) => { - const roles = req?.user?.role; - if (!roles.includes("superadmin")) { - throw this.errorService.createError("Unauthorized", 403); - } - - const userId = req.params.userId; - await editUserByIdParamValidation.validateAsync(req.params); - await editUserPasswordByIdBodyValidation.validateAsync(req.body); - const updatedPassword = req.body.password; - await this.userService.setPasswordByUserId(userId, updatedPassword); - return res.success({ msg: "Password reset successfully" }); - }, - SERVICE_NAME, - "editUserPasswordById" - ); -} - -export default AuthController; diff --git a/server/src/controllers/v1/baseController.js b/server/src/controllers/v1/baseController.js deleted file mode 100644 index e8612cec5..000000000 --- a/server/src/controllers/v1/baseController.js +++ /dev/null @@ -1,83 +0,0 @@ -import { AppError } from "../../service/v1/infrastructure/errorService.js"; - -export const createCommonDependencies = (db, errorService, logger, stringService) => { - return { - db, - errorService, - logger, - stringService, - }; -}; - -class BaseController { - constructor({ db, logger, errorService, ...additionalDependencies }) { - this.db = db; - this.logger = logger; - this.errorService = errorService; - Object.assign(this, additionalDependencies); - - this.asyncHandler = (fn, serviceName, methodName) => { - return async (req, res, next) => { - try { - await fn(req, res, next); - } catch (error) { - // Handle validation errors - if (error.isJoi) { - const validationError = this.errorService.createValidationError(error.message, error.details, serviceName, methodName); - return next(validationError); - } - - if (error.name === "ValidationError") { - const validationError = this.errorService.createValidationError("Database validation failed", error.errors, serviceName, methodName); - return next(validationError); - } - - if (error.name === "CastError") { - const notFoundError = this.errorService.createNotFoundError( - "Invalid resource identifier", - { field: error.path, value: error.value }, - serviceName, - methodName - ); - return next(notFoundError); - } - - if (error.code === "11000") { - const conflictError = this.errorService.createConflictError("Resource already exists", { - originalError: error.message, - code: error.code, - }); - conflictError.service = serviceName; - conflictError.method = methodName; - return next(conflictError); - } - - if (error instanceof AppError) { - error.service = error.service || serviceName; - error.method = error.method || methodName; - return next(error); - } - - if (error.status) { - const appError = this.errorService.createError(error.message, error.status, serviceName, methodName, { - originalError: error.message, - stack: error.stack, - }); - return next(appError); - } - - // For unknown errors, create a server error - const appError = this.errorService.createServerError(error.message || "An unexpected error occurred", { - originalError: error.message, - stack: error.stack, - }); - appError.service = serviceName; - appError.method = methodName; - appError.stack = error.stack; // Preserve original stack - return next(appError); - } - }; - }; - } -} -export default BaseController; diff --git a/server/src/db/v1/MongoDB.js b/server/src/db/MongoDB.js similarity index 100% rename from server/src/db/v1/MongoDB.js rename to server/src/db/MongoDB.js diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts new file mode 100644 index 000000000..d9dd12aa1 --- /dev/null +++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts @@ -0,0 +1,10 @@ +import Monitor from "@/db/models/Monitor.js"; +async function migrateStatusWindowThreshold() { + const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); + for (const monitor of monitors) { + monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; + await monitor.save(); + } +} + +export { migrateStatusWindowThreshold }; diff --git a/server/src/db/migration/0002_convertChecksToTimeSeries.ts b/server/src/db/migration/0002_convertChecksToTimeSeries.ts new file mode 100644 index 000000000..46e6fbc97 --- /dev/null +++ b/server/src/db/migration/0002_convertChecksToTimeSeries.ts @@ -0,0 +1,93 @@ +import mongoose from "mongoose"; + +const CHECKS_COLLECTION = "checks"; +const BACKUP_COLLECTION = "checks_backup"; +const BATCH_SIZE = 1000; + +const getDb = () => { + const db = mongoose.connection.db; + if (!db) { + throw new Error("Database connection is not initialized"); + } + return db; +}; + +const backupAndDropExistingCollection = async () => { + const db = getDb(); + const collections = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (collections.length === 0) { + return false; + } + + await db.collection(CHECKS_COLLECTION).aggregate([ + { $match: {} }, + { $out: BACKUP_COLLECTION }, + ]).toArray(); + await db.collection(CHECKS_COLLECTION).drop(); + return true; +}; + +const createTimeSeriesCollection = async () => { + const db = getDb(); + const existing = await db.listCollections({ name: CHECKS_COLLECTION }).toArray(); + if (existing.length === 0) { + await db.createCollection(CHECKS_COLLECTION, { + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + }); + } + + await db.collection(CHECKS_COLLECTION).createIndexes([ + { key: { updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: 1 } }, + { key: { "metadata.monitorId": 1, updatedAt: -1 } }, + { key: { "metadata.teamId": 1, updatedAt: -1 } }, + ]); +}; + +const migrateBackupData = async () => { + const db = getDb(); + const backupExists = await db.listCollections({ name: BACKUP_COLLECTION }).toArray(); + if (backupExists.length === 0) { + return; + } + + const source = db.collection(BACKUP_COLLECTION); + const target = db.collection(CHECKS_COLLECTION); + const cursor = source.find(); + const operations: any[] = []; + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + if (!doc) { + continue; + } + const metadata = { + monitorId: doc.monitorId, + teamId: doc.teamId, + type: doc.type, + }; + + operations.push({ insertOne: { document: { ...doc, metadata } } }); + + if (operations.length >= BATCH_SIZE) { + await target.bulkWrite(operations); + operations.length = 0; + } + } + + if (operations.length) { + await target.bulkWrite(operations); + } +}; + +export const convertChecksToTimeSeries = async () => { + const backedUp = await backupAndDropExistingCollection(); + await createTimeSeriesCollection(); + if (backedUp) { + await migrateBackupData(); + } +}; diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts new file mode 100644 index 000000000..de2a1dee7 --- /dev/null +++ b/server/src/db/migration/index.ts @@ -0,0 +1,51 @@ +import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; +import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js"; +import MigrationModel from "../models/Migration.js"; + +type MigrationEntry = { + name: string; + execute: () => Promise; +}; + +const migrations: MigrationEntry[] = [ + { name: "0001_migrateStatusWindowThreshold", execute: migrateStatusWindowThreshold }, + { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries }, +]; + +const runMigrations = async (logger?: { info: Function; error: Function }) => { + try { + logger?.info({ message: "Running migrations", service: "Migrations" }); + for (const migration of migrations) { + const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" }); + if (exists) { + logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" }); + continue; + } + + try { + await migration.execute(); + await MigrationModel.findOneAndUpdate( + { name: migration.name }, + { status: "completed", completedAt: new Date(), error: undefined }, + { upsert: true } + ); + logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" }); + } catch (error) { + const err = error as Error; + await MigrationModel.findOneAndUpdate( + { name: migration.name }, + { status: "failed", error: err?.message }, + { upsert: true } + ); + throw error; + } + } + logger?.info({ message: "Migrations completed", service: "Migrations" }); + } catch (error) { + const err = error as Error; + logger?.error({ message: "Migration failed", service: "Migrations", details: err?.message, stack: err?.stack }); + throw error; + } +}; + +export { runMigrations }; diff --git a/server/src/db/v1/models/AppSettings.js b/server/src/db/models/AppSettings.js similarity index 100% rename from server/src/db/v1/models/AppSettings.js rename to server/src/db/models/AppSettings.js diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts new file mode 100755 index 000000000..b13aa9a16 --- /dev/null +++ b/server/src/db/models/Check.ts @@ -0,0 +1,214 @@ +import mongoose from "mongoose"; +import { MonitorTypes } from "@/types/monitor.js"; + +const cpuSchema = new 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 }, + }, + { _id: false } +); + +const memorySchema = new 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 }, + }, + { _id: false } +); + +const diskSchema = new mongoose.Schema( + { + device: { type: String, default: "" }, + mountpoint: { type: String, default: "" }, + 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 }, + }, + { _id: false } +); + +const hostSchema = new mongoose.Schema( + { + os: { type: String, default: "" }, + platform: { type: String, default: "" }, + kernel_version: { type: String, default: "" }, + }, + { _id: false } +); + +const errorSchema = new mongoose.Schema( + { + metric: { type: [String], default: [] }, + err: { type: String, default: "" }, + }, + { _id: false } +); + +const captureSchema = new mongoose.Schema( + { + version: { type: String, default: "" }, + mode: { type: String, default: "" }, + }, + { _id: false } +); + +const networkInterfaceSchema = new mongoose.Schema( + { + name: { type: String }, + bytes_sent: { type: Number, default: 0 }, + bytes_recv: { type: Number, default: 0 }, + packets_sent: { type: Number, default: 0 }, + packets_recv: { type: Number, default: 0 }, + err_in: { type: Number, default: 0 }, + err_out: { type: Number, default: 0 }, + drop_in: { type: Number, default: 0 }, + drop_out: { type: Number, default: 0 }, + fifo_in: { type: Number, default: 0 }, + fifo_out: { type: Number, default: 0 }, + }, + { _id: false } +); + +const metadataSchema = new mongoose.Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + index: true, + }, + type: { + type: String, + enum: MonitorTypes, + required: true, + index: true, + }, + }, + { _id: false } +); + +const CheckSchema = new mongoose.Schema( + { + metadata: { + type: metadataSchema, + required: true, + }, + status: { + type: Boolean, + index: true, + }, + + responseTime: { + type: Number, + }, + + timings: { + type: Object, + default: {}, + }, + + statusCode: { + type: Number, + index: true, + }, + + message: { + type: String, + }, + + expiry: { + type: Date, + default: Date.now, + }, + + ack: { + type: Boolean, + default: false, + }, + + ackAt: { + type: Date, + }, + + // Hardware fields + cpu: { + type: cpuSchema, + default: () => ({}), + }, + memory: { + type: memorySchema, + default: () => ({}), + }, + disk: { + type: [diskSchema], + default: () => [], + }, + host: { + type: hostSchema, + default: () => ({}), + }, + + errors: { + type: [errorSchema], + default: () => [], + }, + + capture: { + type: captureSchema, + default: () => ({}), + }, + + net: { + type: [networkInterfaceSchema], + default: () => [], + }, + + // PageSpeed fields + accessibility: { + type: Number, + }, + bestPractices: { + type: Number, + }, + seo: { + type: Number, + }, + performance: { + type: Number, + }, + audits: { + type: Object, + }, + }, + { + timestamps: true, + strict: false, + timeseries: { + timeField: "createdAt", + metaField: "metadata", + granularity: "seconds", + }, + } +); + +CheckSchema.index({ updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: 1 }); +CheckSchema.index({ "metadata.monitorId": 1, updatedAt: -1 }); +CheckSchema.index({ "metadata.teamId": 1, updatedAt: -1 }); + +export default mongoose.model("Check", CheckSchema); diff --git a/server/src/db/v1/models/Incident.js b/server/src/db/models/Incident.js similarity index 100% rename from server/src/db/v1/models/Incident.js rename to server/src/db/models/Incident.js diff --git a/server/src/db/v1/models/InviteToken.js b/server/src/db/models/InviteToken.js similarity index 100% rename from server/src/db/v1/models/InviteToken.js rename to server/src/db/models/InviteToken.js diff --git a/server/src/db/v1/models/MaintenanceWindow.js b/server/src/db/models/MaintenanceWindow.js similarity index 100% rename from server/src/db/v1/models/MaintenanceWindow.js rename to server/src/db/models/MaintenanceWindow.js diff --git a/server/src/db/models/Migration.ts b/server/src/db/models/Migration.ts new file mode 100644 index 000000000..ba928d910 --- /dev/null +++ b/server/src/db/models/Migration.ts @@ -0,0 +1,13 @@ +import mongoose from "mongoose"; + +const MigrationSchema = new mongoose.Schema( + { + name: { type: String, required: true, unique: true }, + status: { type: String, enum: ["completed", "failed"], default: "completed" }, + completedAt: { type: Date }, + error: { type: String }, + }, + { timestamps: true } +); + +export default mongoose.model("Migration", MigrationSchema); diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/models/Monitor.js similarity index 100% rename from server/src/db/v1/models/Monitor.js rename to server/src/db/models/Monitor.js diff --git a/server/src/db/v1/models/MonitorStats.js b/server/src/db/models/MonitorStats.js similarity index 100% rename from server/src/db/v1/models/MonitorStats.js rename to server/src/db/models/MonitorStats.js diff --git a/server/src/db/v1/models/Notification.js b/server/src/db/models/Notification.js similarity index 100% rename from server/src/db/v1/models/Notification.js rename to server/src/db/models/Notification.js diff --git a/server/src/db/v1/models/RecoveryToken.js b/server/src/db/models/RecoveryToken.js similarity index 100% rename from server/src/db/v1/models/RecoveryToken.js rename to server/src/db/models/RecoveryToken.js diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/models/StatusPage.js similarity index 100% rename from server/src/db/v1/models/StatusPage.js rename to server/src/db/models/StatusPage.js diff --git a/server/src/db/v1/models/Team.js b/server/src/db/models/Team.js similarity index 100% rename from server/src/db/v1/models/Team.js rename to server/src/db/models/Team.js diff --git a/server/src/db/v1/models/User.js b/server/src/db/models/User.js similarity index 100% rename from server/src/db/v1/models/User.js rename to server/src/db/models/User.js diff --git a/server/src/db/v1/modules/checkModule.js b/server/src/db/modules/checkModule.js similarity index 100% rename from server/src/db/v1/modules/checkModule.js rename to server/src/db/modules/checkModule.js diff --git a/server/src/db/v1/modules/checkModuleQueries.js b/server/src/db/modules/checkModuleQueries.js similarity index 100% rename from server/src/db/v1/modules/checkModuleQueries.js rename to server/src/db/modules/checkModuleQueries.js diff --git a/server/src/db/v1/modules/incidentModule.js b/server/src/db/modules/incidentModule.js similarity index 100% rename from server/src/db/v1/modules/incidentModule.js rename to server/src/db/modules/incidentModule.js diff --git a/server/src/db/v1/modules/inviteModule.js b/server/src/db/modules/inviteModule.js similarity index 100% rename from server/src/db/v1/modules/inviteModule.js rename to server/src/db/modules/inviteModule.js diff --git a/server/src/db/v1/modules/maintenanceWindowModule.js b/server/src/db/modules/maintenanceWindowModule.js similarity index 100% rename from server/src/db/v1/modules/maintenanceWindowModule.js rename to server/src/db/modules/maintenanceWindowModule.js diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/modules/monitorModule.js similarity index 100% rename from server/src/db/v1/modules/monitorModule.js rename to server/src/db/modules/monitorModule.js diff --git a/server/src/db/v1/modules/monitorModuleQueries.js b/server/src/db/modules/monitorModuleQueries.js similarity index 100% rename from server/src/db/v1/modules/monitorModuleQueries.js rename to server/src/db/modules/monitorModuleQueries.js diff --git a/server/src/db/v1/modules/notificationModule.js b/server/src/db/modules/notificationModule.js similarity index 100% rename from server/src/db/v1/modules/notificationModule.js rename to server/src/db/modules/notificationModule.js diff --git a/server/src/db/v1/modules/recoveryModule.js b/server/src/db/modules/recoveryModule.js similarity index 100% rename from server/src/db/v1/modules/recoveryModule.js rename to server/src/db/modules/recoveryModule.js diff --git a/server/src/db/v1/modules/settingsModule.js b/server/src/db/modules/settingsModule.js similarity index 100% rename from server/src/db/v1/modules/settingsModule.js rename to server/src/db/modules/settingsModule.js diff --git a/server/src/db/v1/modules/statusPageModule.js b/server/src/db/modules/statusPageModule.js similarity index 100% rename from server/src/db/v1/modules/statusPageModule.js rename to server/src/db/modules/statusPageModule.js diff --git a/server/src/db/v1/modules/userModule.js b/server/src/db/modules/userModule.js similarity index 100% rename from server/src/db/v1/modules/userModule.js rename to server/src/db/modules/userModule.js diff --git a/server/src/db/v1/utils/seedDb.js b/server/src/db/utils/seedDb.js similarity index 100% rename from server/src/db/v1/utils/seedDb.js rename to server/src/db/utils/seedDb.js diff --git a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts deleted file mode 100644 index 55d26cc27..000000000 --- a/server/src/db/v1/migration/0001_migrateStatusWindowThreshold.ts +++ /dev/null @@ -1,18 +0,0 @@ -// import Monitor from "@/db/v1/models/Monitor.js"; -async function migrateStatusWindowThreshold() { - try { - // const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); - // for (const monitor of monitors) { - // monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100; - // await monitor.save(); - // console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`); - // } - // console.log("StatusWindowThreshold migration complete."); - return true; - } catch (err) { - console.error("Migration error:", err); - return false; - } -} - -export { migrateStatusWindowThreshold }; diff --git a/server/src/db/v1/migration/index.ts b/server/src/db/v1/migration/index.ts deleted file mode 100644 index 4bf56895a..000000000 --- a/server/src/db/v1/migration/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); -}; - -export { runMigrations }; diff --git a/server/src/db/v1/models/Check.js b/server/src/db/v1/models/Check.js deleted file mode 100755 index 7a5117979..000000000 --- a/server/src/db/v1/models/Check.js +++ /dev/null @@ -1,178 +0,0 @@ -import mongoose from "mongoose"; - -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({ - device: { type: String, default: "" }, - mountpoint: { type: String, default: "" }, - 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 captureSchema = mongoose.Schema({ - version: { type: String, default: "" }, - mode: { type: String, default: "" }, -}); - -const networkInterfaceSchema = mongoose.Schema({ - name: { type: String }, - bytes_sent: { type: Number, default: 0 }, - bytes_recv: { type: Number, default: 0 }, - packets_sent: { type: Number, default: 0 }, - packets_recv: { type: Number, default: 0 }, - err_in: { type: Number, default: 0 }, - err_out: { type: Number, default: 0 }, - drop_in: { type: Number, default: 0 }, - drop_out: { type: Number, default: 0 }, - fifo_in: { type: Number, default: 0 }, - fifo_out: { type: Number, default: 0 }, -}); - -const CheckSchema = new mongoose.Schema( - { - // Common fields - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - index: true, - }, - type: { - type: String, - enum: ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"], - required: true, - index: true, - }, - - status: { - type: Boolean, - index: true, - }, - - responseTime: { - type: Number, - }, - - timings: { - type: Object, - default: {}, - }, - - statusCode: { - type: Number, - index: true, - }, - - message: { - type: String, - }, - - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, - - ack: { - type: Boolean, - default: false, - }, - - ackAt: { - type: Date, - }, - - // Hardware fields - cpu: { - type: cpuSchema, - default: () => ({}), - }, - memory: { - type: memorySchema, - default: () => ({}), - }, - disk: { - type: [diskSchema], - default: () => [], - }, - host: { - type: hostSchema, - default: () => ({}), - }, - - errors: { - type: [errorSchema], - default: () => [], - }, - - capture: { - type: captureSchema, - default: () => ({}), - }, - - net: { - type: [networkInterfaceSchema], - default: () => [], - }, - - // PageSpeed fields - accessibility: { - type: Number, - }, - bestPractices: { - type: Number, - }, - seo: { - type: Number, - }, - performance: { - type: Number, - }, - audits: { - type: Object, - }, - }, - { timestamps: true } -); - -CheckSchema.index({ updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: 1 }); -CheckSchema.index({ monitorId: 1, updatedAt: -1 }); -CheckSchema.index({ teamId: 1, updatedAt: -1 }); - -export default mongoose.model("Check", CheckSchema); diff --git a/server/src/index.js b/server/src/index.js index e8b756d0e..28dc5e8fd 100755 --- a/server/src/index.js +++ b/server/src/index.js @@ -5,10 +5,11 @@ import { initShutdownListener } from "./shutdown.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; +import { runMigrations } from "./db/migration/index.js"; import Logger from "./utils/logger.js"; -import SettingsService from "./service/v1/system/settingsService.js"; -import AppSettings from "./db/v1/models/AppSettings.js"; +import SettingsService from "./service/system/settingsService.js"; +import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; let logger; @@ -30,6 +31,8 @@ const startApp = async () => { // Initialize services const services = await initializeServices({ logger, envSettings, settingsService }); + await runMigrations(logger); + // Initialize controllers const controllers = initializeControllers(services); diff --git a/server/src/middleware/v1/handleErrors.js b/server/src/middleware/v1/handleErrors.js index 43671fbe2..0a30b0420 100755 --- a/server/src/middleware/v1/handleErrors.js +++ b/server/src/middleware/v1/handleErrors.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; const handleErrors = (error, req, res, next) => { const status = error.status || 500; diff --git a/server/src/middleware/v1/isAllowed.js b/server/src/middleware/v1/isAllowed.js index cc240f531..1d0de42b4 100755 --- a/server/src/middleware/v1/isAllowed.js +++ b/server/src/middleware/v1/isAllowed.js @@ -1,9 +1,9 @@ import jwt from "jsonwebtoken"; const TOKEN_PREFIX = "Bearer "; const SERVICE_NAME = "allowedRoles"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; +import SettingsService from "../../service/system/settingsService.js"; const isAllowed = (allowedRoles) => { return (req, res, next) => { diff --git a/server/src/middleware/v1/verifyJWT.js b/server/src/middleware/v1/verifyJWT.js index b57391dfd..cf983a6c8 100755 --- a/server/src/middleware/v1/verifyJWT.js +++ b/server/src/middleware/v1/verifyJWT.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import SettingsService from "../../service/v1/system/settingsService.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import SettingsService from "../../service/system/settingsService.js"; +import StringService from "../../service/system/stringService.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/v1/verifyOwnership.js b/server/src/middleware/v1/verifyOwnership.js index 741fb6436..6fcc84f60 100755 --- a/server/src/middleware/v1/verifyOwnership.js +++ b/server/src/middleware/v1/verifyOwnership.js @@ -1,6 +1,6 @@ import { logger } from "../../utils/logger.js"; -import ServiceRegistry from "../../service/v1/system/serviceRegistry.js"; -import StringService from "../../service/v1/system/stringService.js"; +import ServiceRegistry from "../../service/system/serviceRegistry.js"; +import StringService from "../../service/system/stringService.js"; import { ObjectId } from "mongodb"; const SERVICE_NAME = "verifyOwnership"; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/repositories/checks/IChecksRepository.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts new file mode 100644 index 000000000..e69de29bb diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts new file mode 100644 index 000000000..458ee5bad --- /dev/null +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -0,0 +1,9 @@ +import type { Monitor } from "@/types/index.js"; +export interface IMonitorsRepository { + // create + // single fetch + // collection fetch + findAll(): Promise; + // update + // delete +} diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts new file mode 100644 index 000000000..b4bae06c8 --- /dev/null +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -0,0 +1,5 @@ +class MongoMonitorsRepository implements IMonitorsRepository { + findAll = async () => { + return null; + }; +} diff --git a/server/src/routes/v1/announcementsRoute.js b/server/src/routes/v1/announcementsRoute.js deleted file mode 100755 index 011dfac4a..000000000 --- a/server/src/routes/v1/announcementsRoute.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Router } from "express"; -import { verifyJWT } from "../middleware/verifyJWT.js"; -import { isAllowed } from "../middleware/isAllowed.js"; - -class AnnouncementRoutes { - constructor(controller) { - this.router = Router(); - this.announcementController = controller; - this.initRoutes(); - } - - initRoutes() { - /** - * @route POST / - * @desc Create a new announcement - * @access Private (Requires JWT verification) - */ - this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.announcementController.createAnnouncement); - - /** - * @route GET / - * @desc Get announcements - * @access Public - */ - this.router.get("/", this.announcementController.getAnnouncement); - } - - getRouter() { - return this.router; - } -} - -export default AnnouncementRoutes; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index fe430d46f..86463b6bb 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -1,7 +1,7 @@ import { Router } from "express"; import { isAllowed } from "../../middleware/v1/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/v1/controllerUtils.js"; +import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); diff --git a/server/src/service/v1/business/checkService.js b/server/src/service/business/checkService.js similarity index 100% rename from server/src/service/v1/business/checkService.js rename to server/src/service/business/checkService.js diff --git a/server/src/service/v1/business/diagnosticService.js b/server/src/service/business/diagnosticService.js similarity index 100% rename from server/src/service/v1/business/diagnosticService.js rename to server/src/service/business/diagnosticService.js diff --git a/server/src/service/v1/business/incidentService.js b/server/src/service/business/incidentService.js similarity index 100% rename from server/src/service/v1/business/incidentService.js rename to server/src/service/business/incidentService.js diff --git a/server/src/service/v1/business/inviteService.js b/server/src/service/business/inviteService.js similarity index 100% rename from server/src/service/v1/business/inviteService.js rename to server/src/service/business/inviteService.js diff --git a/server/src/service/v1/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js similarity index 100% rename from server/src/service/v1/business/maintenanceWindowService.js rename to server/src/service/business/maintenanceWindowService.js diff --git a/server/src/service/v1/business/monitorService.js b/server/src/service/business/monitorService.js similarity index 99% rename from server/src/service/v1/business/monitorService.js rename to server/src/service/business/monitorService.js index 2118e6281..4eae0176f 100644 --- a/server/src/service/v1/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -1,4 +1,4 @@ -import { createMonitorsBodyValidation } from "../../../validation/joi.js"; +import { createMonitorsBodyValidation } from "@/validation/joi.js"; const SERVICE_NAME = "MonitorService"; class MonitorService { diff --git a/server/src/service/v1/business/userService.js b/server/src/service/business/userService.js similarity index 100% rename from server/src/service/v1/business/userService.js rename to server/src/service/business/userService.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js similarity index 100% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js diff --git a/server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js similarity index 100% rename from server/src/service/v1/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js rename to server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js diff --git a/server/src/service/v1/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js similarity index 100% rename from server/src/service/v1/infrastructure/bufferService.js rename to server/src/service/infrastructure/bufferService.js diff --git a/server/src/service/v1/infrastructure/emailService.js b/server/src/service/infrastructure/emailService.js similarity index 100% rename from server/src/service/v1/infrastructure/emailService.js rename to server/src/service/infrastructure/emailService.js diff --git a/server/src/service/v1/infrastructure/errorService.js b/server/src/service/infrastructure/errorService.js similarity index 100% rename from server/src/service/v1/infrastructure/errorService.js rename to server/src/service/infrastructure/errorService.js diff --git a/server/src/service/v1/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js similarity index 100% rename from server/src/service/v1/infrastructure/networkService.js rename to server/src/service/infrastructure/networkService.js diff --git a/server/src/service/v1/infrastructure/notificationProviders/matrix.js b/server/src/service/infrastructure/notificationProviders/matrix.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationProviders/matrix.js rename to server/src/service/infrastructure/notificationProviders/matrix.js diff --git a/server/src/service/v1/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationService.js rename to server/src/service/infrastructure/notificationService.js diff --git a/server/src/service/v1/infrastructure/notificationUtils.js b/server/src/service/infrastructure/notificationUtils.js similarity index 100% rename from server/src/service/v1/infrastructure/notificationUtils.js rename to server/src/service/infrastructure/notificationUtils.js diff --git a/server/src/service/v1/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js similarity index 98% rename from server/src/service/v1/infrastructure/statusService.js rename to server/src/service/infrastructure/statusService.js index 829b6d850..3c1682818 100755 --- a/server/src/service/v1/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -1,5 +1,5 @@ -import MonitorStats from "../../../db/v1/models/MonitorStats.js"; -import Check from "../../../db/v1/models/Check.js"; +import MonitorStats from "../../db/models/MonitorStats.js"; +import Check from "../../db/models/Check.js"; const SERVICE_NAME = "StatusService"; class StatusService { @@ -337,9 +337,11 @@ class StatusService { } = networkResponse; const check = { - monitorId, - teamId, - type, + metadata: { + monitorId, + teamId, + type, + }, status, statusCode: code, responseTime, diff --git a/server/src/service/v1/system/serviceRegistry.js b/server/src/service/system/serviceRegistry.js similarity index 100% rename from server/src/service/v1/system/serviceRegistry.js rename to server/src/service/system/serviceRegistry.js diff --git a/server/src/service/v1/system/settingsService.js b/server/src/service/system/settingsService.js similarity index 100% rename from server/src/service/v1/system/settingsService.js rename to server/src/service/system/settingsService.js diff --git a/server/src/service/v1/system/stringService.js b/server/src/service/system/stringService.js similarity index 100% rename from server/src/service/v1/system/stringService.js rename to server/src/service/system/stringService.js diff --git a/server/src/service/v1/system/translationService.js b/server/src/service/system/translationService.js similarity index 100% rename from server/src/service/v1/system/translationService.js rename to server/src/service/system/translationService.js diff --git a/server/src/types/check.ts b/server/src/types/check.ts new file mode 100644 index 000000000..f2ccc0b77 --- /dev/null +++ b/server/src/types/check.ts @@ -0,0 +1,79 @@ +import type { MonitorType } from "@/types/index.js"; + +export interface CheckTimingPhases { + wait: number; + dns: number; + tcp: number; + tls: number; + request: number; + firstByte: number; + download: number; + total: number; +} + +export interface CheckTimings { + start: number; + socket: number; + lookup: number; + connect: number; + secureConnect: number; + upload: number; + response: number; + end: number; + phases: CheckTimingPhases; +} + +export interface CheckCpuInfo { + physical_core: number; + logical_core: number; + frequency: number; + temperature: number[]; + free_percent: number; + usage_percent: number; +} + +export interface CheckMemoryInfo { + total_bytes: number; + available_bytes: number; + used_bytes: number; + usage_percent: number; +} + +export interface CheckHostInfo { + os: string; + platform: string; + kernel_version: string; +} + +export interface CheckCaptureInfo { + version: string; + mode: string; +} + +export interface CheckDiskInfo {} + +export interface CheckNetInfo {} + +export interface Check { + id: string; + monitorId: string; + teamId: string; + type: MonitorType; + status: boolean; + responseTime: number; + timings: CheckTimings; + statusCode: number; + message: string; + ack: boolean; + expiry: string; + cpu: CheckCpuInfo; + memory: CheckMemoryInfo; + disk: CheckDiskInfo[]; + host: CheckHostInfo; + errors: string[]; + capture: CheckCaptureInfo; + net: CheckNetInfo[]; + __v: number; + createdAt: string; + updatedAt: string; +} diff --git a/server/src/types/index.ts b/server/src/types/index.ts new file mode 100644 index 000000000..92bcb69b0 --- /dev/null +++ b/server/src/types/index.ts @@ -0,0 +1,2 @@ +export * from "@/types/check.js"; +export * from "@/types/monitor.js"; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts new file mode 100644 index 000000000..6a386c2e4 --- /dev/null +++ b/server/src/types/monitor.ts @@ -0,0 +1,29 @@ +export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game"] as const; +export type MonitorType = (typeof MonitorTypes)[number]; + +export interface Monitor { + id: string; + userId: string; + teamId: string; + name: string; + description: string; + statusWindow: boolean[]; + statusWindowSize: number; + statusWindowThreshold: number; + type: MonitorType; + ignoreTlsErrors: boolean; + url: string; + isActive: boolean; + interval: number; + notifications: string[]; + alertThreshold: number; + selectedDisks: string[]; + group: string | null; + cpuAlertThreshold: number; + memoryAlertThreshold: number; + diskAlertThreshold: number; + tempAlertThreshold: number; + createdAt: string; + updatedAt: string; + status: boolean; +}