diff --git a/server/openapi.json b/server/openapi.json index f3a6a2505..d0bbe45dc 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -386,6 +386,10 @@ "type": "string", "enum": ["equal", "include", "regex"] }, + "method": { + "type": "string", + "enum": ["GET", "HEAD"] + }, "notifications": { "type": "array", "items": { @@ -466,6 +470,9 @@ }, "updatedAt": { "type": "string" + }, + "lastEvaluatedAt": { + "type": "number" } }, "required": [ @@ -480,6 +487,7 @@ "statusWindowThreshold", "ignoreTlsErrors", "useAdvancedMatching", + "method", "notifications", "tags", "cpuAlertThreshold", @@ -493,7 +501,8 @@ "teamId", "userId", "createdAt", - "updatedAt" + "updatedAt", + "lastEvaluatedAt" ], "additionalProperties": { "nullable": true @@ -7066,6 +7075,10 @@ } ] }, + "method": { + "type": "string", + "enum": ["GET", "HEAD"] + }, "gameId": { "anyOf": [ { @@ -7666,6 +7679,11 @@ } ] }, + "method": { + "type": "string", + "enum": ["GET", "HEAD"], + "default": "GET" + }, "url": { "type": "string", "minLength": 1 @@ -8278,6 +8296,10 @@ } ] }, + "method": { + "type": "string", + "enum": ["GET", "HEAD"] + }, "port": { "type": "number", "minimum": 1, diff --git a/server/src/app.ts b/server/src/app.ts index 907c0f77d..d559edb1f 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -9,20 +9,20 @@ import { handleErrors } from "@/api/middleware/handleErrors.js"; import { generalApiLimiter } from "@/api/middleware/rateLimiter.js"; import { sanitizeBody, sanitizeQuery } from "@/api/middleware/sanitization.js"; import { setupRoutes } from "@/config/routes.js"; -import { InitializedServices } from "@/config/services.js"; import { InitializedControllers } from "@/config/controllers.js"; import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; import { createStatusPageCorsOrigin } from "@/api/middleware/statusPageCorsOrigin.js"; import { isPublicStatusPageApiPath } from "@/api/middleware/statusPagePublicApiPath.js"; +import { ApiServices } from "@/config/services.api.js"; export const createApp = ({ - services, + apiServices: services, controllers, envSettings, frontendPath, openApiSpec, }: { - services: InitializedServices; + apiServices: ApiServices; controllers: InitializedControllers; envSettings: EnvConfig; frontendPath: string; diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts index cdbb876b8..7646b41db 100644 --- a/server/src/config/controllers.ts +++ b/server/src/config/controllers.ts @@ -12,7 +12,7 @@ import NotificationController from "../api/controllers/notificationController.js import TagsController from "../api/controllers/tagController.js"; import DiagnosticController from "../api/controllers/diagnosticController.js"; import IncidentController from "../api/controllers/incidentController.js"; -import type { InitializedServices } from "@/config/services.js"; +import { ApiServices } from "@/config/services.api.js"; export interface InitializedControllers { authController: AuthController; @@ -30,21 +30,21 @@ export interface InitializedControllers { diagnosticController: DiagnosticController; incidentController: IncidentController; } -export const initializeControllers = (services: InitializedServices): InitializedControllers => { +export const initializeControllers = (apiServices: ApiServices): InitializedControllers => { return { - authController: new AuthController(services.userService), - monitorController: new MonitorController(services.monitorService, services.notificationsService), - settingsController: new SettingsController(services.settingsService, services.emailService), - checkController: new CheckController(services.checkService), - geoCheckController: new GeoCheckController(services.geoChecksService), - inviteController: new InviteController(services.inviteService), - maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService), - queueController: new QueueController(services.worker), - logController: new LogController(services.logger), - statusPageController: new StatusPageController(services.statusPageService, services.monitorsRepository, services.settingsService), - notificationController: new NotificationController(services.notificationsService, services.monitorsRepository), - tagController: new TagsController(services.tagsService), - diagnosticController: new DiagnosticController(services.diagnosticService), - incidentController: new IncidentController(services.incidentService), + authController: new AuthController(apiServices.userService), + monitorController: new MonitorController(apiServices.monitorService, apiServices.notificationsService), + settingsController: new SettingsController(apiServices.settingsService, apiServices.emailService), + checkController: new CheckController(apiServices.checkService), + geoCheckController: new GeoCheckController(apiServices.geoChecksService), + inviteController: new InviteController(apiServices.inviteService), + maintenanceWindowController: new MaintenanceWindowController(apiServices.maintenanceWindowService), + queueController: new QueueController(apiServices.worker), + logController: new LogController(apiServices.logger), + statusPageController: new StatusPageController(apiServices.statusPageService, apiServices.monitorsRepository, apiServices.settingsService), + notificationController: new NotificationController(apiServices.notificationsService, apiServices.monitorsRepository), + tagController: new TagsController(apiServices.tagsService), + diagnosticController: new DiagnosticController(apiServices.diagnosticService), + incidentController: new IncidentController(apiServices.incidentService), }; }; diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index d3fbab2c7..855c18abb 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -3,7 +3,7 @@ import { createVerifyJWT } from "../api/middleware/verifyJWT.js"; import { createVerifyStatusPageAccess } from "../api/middleware/verifyStatusPageAccess.js"; import { authApiLimiter } from "../api/middleware/rateLimiter.js"; import type { InitializedControllers } from "./controllers.js"; -import type { InitializedServices } from "./services.js"; +import type { ApiServices } from "@/config/services.api.js"; import AuthRoutes from "../api/routes/authRoute.js"; import InviteRoutes from "../api/routes/inviteRoute.js"; @@ -21,8 +21,8 @@ import TagRoutes from "../api/routes/tagRoutes.js"; import IncidentRoutes from "../api/routes/incidentRoute.js"; -export const setupRoutes = (app: Application, controllers: InitializedControllers, services: InitializedServices) => { - const verifyJWT = createVerifyJWT(services.settingsService); +export const setupRoutes = (app: Application, controllers: InitializedControllers, apiServices: ApiServices) => { + const verifyJWT = createVerifyJWT(apiServices.settingsService); const authRoutes = new AuthRoutes(controllers.authController, verifyJWT); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); @@ -32,7 +32,7 @@ export const setupRoutes = (app: Application, controllers: InitializedController const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController); const queueRoutes = new QueueRoutes(controllers.queueController); const logRoutes = new LogRoutes(controllers.logController); - const verifyStatusPageAccess = createVerifyStatusPageAccess(services.statusPagesRepository, verifyJWT); + const verifyStatusPageAccess = createVerifyStatusPageAccess(apiServices.statusPagesRepository, verifyJWT); const statusPageRoutes = new StatusPageRoutes(controllers.statusPageController, verifyJWT, verifyStatusPageAccess); const notificationRoutes = new NotificationRoutes(controllers.notificationController); const tagRoutes = new TagRoutes(controllers.tagController); diff --git a/server/src/config/services.api.ts b/server/src/config/services.api.ts index e69de29bb..0b9361e5d 100644 --- a/server/src/config/services.api.ts +++ b/server/src/config/services.api.ts @@ -0,0 +1,109 @@ +import { DiagnosticService, IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js"; +import { IInviteService, InviteService } from "@/domain/invites/invite.service.js"; +import { IMaintenanceWindowService, MaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js"; +import { IMonitorService, MonitorService } from "@/domain/monitors/monitor.service.js"; +import { IStatusPageService, StatusPageService } from "@/domain/status-pages/status-page.service.js"; +import { ITagsService, TagsService } from "@/domain/tags/tag.service.js"; +import { IUserService, UserService } from "@/domain/users/user.service.js"; +import { IJobScheduler } from "@/worker/worker.interface.js"; + +// Third-party +import jwt from "jsonwebtoken"; +import crypto from "crypto"; +import { games } from "gamedig"; + +import { SharedServices } from "@/config/services.shared.js"; + +export interface ApiServices extends SharedServices { + worker: IJobScheduler; // control-plane handle only (DBQueueWorker in all-in-one, bare JobScheduler in API-only) + userService: IUserService; + monitorService: IMonitorService; + maintenanceWindowService: IMaintenanceWindowService; + inviteService: IInviteService; + statusPageService: IStatusPageService; + tagsService: ITagsService; + diagnosticService: IDiagnosticService; +} + +export const buildApi = (shared: SharedServices, jobScheduler: IJobScheduler): ApiServices => { + const { + logger, + db, + settingsService, + emailService, + monitorsRepository, + checksRepository, + geoChecksRepository, + monitorStatsRepository, + statusPagesRepository, + usersRepository, + invitesRepository, + recoveryTokensRepository, + settingsRepository, + tagsRepository, + incidentsRepository, + teamsRepository, + maintenanceWindowsRepository, + jobsRepository, + } = shared; + + const userService = new UserService({ + crypto, + emailService, + settingsService, + logger, + jwt, + scheduler: jobScheduler, + monitorsRepository, + usersRepository, + invitesRepository, + recoveryTokensRepository, + settingsRepository, + teamsRepository, + }); + + // *********************** + // Business services + // *********************** + + const monitorService = new MonitorService({ + scheduler: jobScheduler, + logger, + games, + monitorsRepository, + checksRepository, + geoChecksRepository, + monitorStatsRepository, + statusPagesRepository, + incidentsRepository, + }); + + const maintenanceWindowService = new MaintenanceWindowService({ + monitorsRepository, + maintenanceWindowsRepository, + jobsRepository, + scheduler: jobScheduler, + }); + + const inviteService = new InviteService({ + invitesRepository, + settingsService, + emailService, + }); + + const statusPageService = new StatusPageService(statusPagesRepository, settingsService); + const tagsService = new TagsService(tagsRepository, monitorsRepository); + const diagnosticService = new DiagnosticService(db); + + return { + ...shared, + worker: jobScheduler, + userService, + monitorService, + maintenanceWindowService, + inviteService, + statusPageService, + tagsService, + diagnosticService, + }; +}; diff --git a/server/src/config/services.ts b/server/src/config/services.ts deleted file mode 100644 index 78dabadf8..000000000 --- a/server/src/config/services.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { Mongoose } from "mongoose"; -import { buildShared } from "@/config/services.shared.js"; -import { IDb } from "@/db/db.interface.js"; -import { EnvConfig, ISettingsService } from "@/domain/app-settings/app-settings.service.js"; -import { ICheckService } from "@/domain/checks/check.service.js"; -import { DiagnosticService, IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js"; -import { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js"; -import { IIncidentService } from "@/domain/incidents/incident.service.js"; -import { IInviteService, InviteService } from "@/domain/invites/invite.service.js"; -import { IMaintenanceWindowService, MaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js"; -import { IMonitorService, MonitorService } from "@/domain/monitors/monitor.service.js"; -import { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js"; -import { INotificationsService } from "@/domain/notifications/notification.service.js"; -import { IStatusPageService, StatusPageService } from "@/domain/status-pages/status-page.service.js"; -import { ITagsService, TagsService } from "@/domain/tags/tag.service.js"; -import { IUserService, UserService } from "@/domain/users/user.service.js"; -import { BufferService, IBufferService } from "@/service/bufferService.js"; -import { IEmailService } from "@/service/emailService.js"; -import { WorkerHelper } from "@/worker/worker.helper.js"; -import { IQueueWorker } from "@/worker/worker.interface.js"; -import { INetworkService, NetworkService } from "@/service/networkService.js"; -import { IStatusService, StatusService } from "@/service/statusService.js"; -import { MonitorStatusPolicy } from "@/worker/worker.monitor-status-policy.js"; - -// Network providers -import { PingProvider } from "@/service/network/PingProvider.js"; -import { HttpProvider } from "@/service/network/HttpProvider.js"; -import { AdvancedMatcher } from "@/service/network/AdvancedMatcher.js"; -import { PageSpeedProvider } from "@/service/network/PageSpeedProvider.js"; -import { HardwareProvider } from "@/service/network/HardwareProvider.js"; -import { DockerProvider } from "@/service/network/DockerProvider.js"; -import { PortProvider } from "@/service/network/PortProvider.js"; -import { GameProvider } from "@/service/network/GameProvider.js"; -import { GrpcProvider } from "@/service/network/GrpcProvider.js"; -import { WebSocketProvider } from "@/service/network/WebSocketProvider.js"; -import { DNSProvider } from "@/service/network/DNSProvider.js"; - -// Third-party -import { Resolver } from "dns/promises"; -import axios from "axios"; -import got from "got"; -import ping from "ping"; -import Docker from "dockerode"; -import net from "net"; -import jwt from "jsonwebtoken"; -import crypto from "crypto"; -import { games, GameDig } from "gamedig"; -import jmespath from "jmespath"; -import * as grpc from "@grpc/grpc-js"; -import * as protoLoader from "@grpc/proto-loader"; -import WebSocket from "ws"; - -// Repositories -import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js"; -import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; -import { IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js"; -import { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; -import { IInvitesRepository } from "@/domain/invites/invite.repository.interface.js"; -import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js"; -import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; -import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; -import { INotificationsRepository } from "@/domain/notifications/notification.repository.interface.js"; -import { IRecoveryTokensRepository } from "@/domain/recovery-tokens/recovery-token.repository.interface.js"; -import { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; -import { ITagsRepository } from "@/domain/tags/tag.repository.interface.js"; -import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; -import { IUsersRepository } from "@/domain/users/user.repository.interface.js"; -import { ILogger } from "@/utils/logger.js"; -import { NotificationReactor } from "@/worker/reactors/reactor.notification.js"; -import { ReactorDispatcher } from "@/worker/reactors/reactor.dispatcher.js"; -import { IncidentReactor } from "@/worker/reactors/reactor.incident.js"; -import { GeoChecksPipeline } from "@/worker/worker.check-pipeline.js"; -import { CheckProducer } from "@/worker/worker.check-producer.js"; -import { CheckEvaluator } from "@/worker/worker.check-evaluator.js"; -import { DBQueueWorker } from "@/worker/worker.db-queue.js"; - -export type InitializedServices = { - settingsService: ISettingsService; - db: IDb; - networkService: INetworkService; - emailService: IEmailService; - bufferService: IBufferService; - statusService: IStatusService; - worker: IQueueWorker; - userService: IUserService; - checkService: ICheckService; - geoChecksService: IGeoChecksService; - diagnosticService: IDiagnosticService; - inviteService: IInviteService; - maintenanceWindowService: IMaintenanceWindowService; - monitorService: IMonitorService; - incidentService: IIncidentService; - logger: ILogger; - notificationsService: INotificationsService; - tagsService: ITagsService; - statusPageService: IStatusPageService; - notificationMessageBuilder: INotificationMessageBuilder; - - // Repositories - monitorsRepository: IMonitorsRepository; - checksRepository: IChecksRepository; - geoChecksRepository: IGeoChecksRepository; - monitorStatsRepository: IMonitorStatsRepository; - statusPagesRepository: IStatusPagesRepository; - usersRepository: IUsersRepository; - invitesRepository: IInvitesRepository; - recoveryTokensRepository: IRecoveryTokensRepository; - settingsRepository: ISettingsRepository; - notificationsRepository: INotificationsRepository; - tagsRepository: ITagsRepository; - incidentsRepository: IIncidentsRepository; - teamsRepository: ITeamsRepository; - maintenanceWindowsRepository: IMaintenanceWindowsRepository; -}; - -export const initializeServices = async ({ - logger, - envSettings, - settingsService, -}: { - logger: ILogger; - envSettings: EnvConfig; - settingsService: ISettingsService; -}): Promise => { - const shared = await buildShared({ logger, envSettings, settingsService }); - const { - db, - emailService, - notificationMessageBuilder, - incidentService, - checkService, - geoChecksService, - notificationsService, - workerId, - jobsRepository, - monitorsRepository, - queueWorkersRepository, - checksRepository, - geoChecksRepository, - monitorStatsRepository, - statusPagesRepository, - usersRepository, - invitesRepository, - recoveryTokensRepository, - settingsRepository, - notificationsRepository, - tagsRepository, - incidentsRepository, - teamsRepository, - maintenanceWindowsRepository, - } = shared; - - // Network providers - const pingProvider = new PingProvider(ping); - const httpProvider = new HttpProvider(got, new AdvancedMatcher(jmespath)); - const pageSpeedProvider = new PageSpeedProvider(httpProvider, settingsService, logger); - const hardwareProvider = new HardwareProvider(httpProvider); - const dockerProvider = new DockerProvider(logger, Docker); - const portProvider = new PortProvider(net); - const gameProvider = new GameProvider(logger, GameDig); - const grpcProvider = new GrpcProvider(grpc, protoLoader); - const webSocketProvider = new WebSocketProvider(WebSocket); - const dnsProvider = new DNSProvider(() => new Resolver()); - - const networkService = new NetworkService(axios, logger, [ - pingProvider, - httpProvider, - pageSpeedProvider, - hardwareProvider, - dockerProvider, - portProvider, - gameProvider, - grpcProvider, - webSocketProvider, - dnsProvider, - ]); - - const bufferService = new BufferService(logger, checkService, geoChecksService, settingsService, jobsRepository); - - const statusService = new StatusService(logger, monitorsRepository, monitorStatsRepository); - - const tagsService = new TagsService(tagsRepository, monitorsRepository); - - const monitorStatusPolicy = new MonitorStatusPolicy(); - // reactors and dispatcher - const notificationReactor = new NotificationReactor(notificationsService); - const incidentReactor = new IncidentReactor(incidentService); - const reactorDispatcher = new ReactorDispatcher(logger, [notificationReactor, incidentReactor]); - - // Check producer/evaluator - const checkProducer = new CheckProducer(monitorsRepository, maintenanceWindowsRepository, checkService, networkService, bufferService, logger); - const checkEvaluator = new CheckEvaluator(statusService, monitorStatusPolicy); - - // pipelines - const geoCheckPipeline = new GeoChecksPipeline(maintenanceWindowsRepository, geoChecksService, bufferService, logger); - - const workerHelper = new WorkerHelper( - logger, - checkService, - settingsService, - monitorsRepository, - jobsRepository, - teamsRepository, - monitorStatsRepository, - checksRepository, - incidentsRepository, - geoChecksRepository - ); - - const worker = await DBQueueWorker.create( - logger, - jobsRepository, - monitorsRepository, - checksRepository, - checkService, - checkProducer, - checkEvaluator, - geoCheckPipeline, - reactorDispatcher, - workerHelper, - queueWorkersRepository, - envSettings.queueMode, - envSettings.queuePrimaryProcesses, - workerId - ); - - // Business services - const userService = new UserService({ - crypto, - emailService, - settingsService, - logger, - jwt, - scheduler: worker, - monitorsRepository, - usersRepository, - invitesRepository, - recoveryTokensRepository, - settingsRepository, - teamsRepository, - }); - - const diagnosticService = new DiagnosticService(db); - const inviteService = new InviteService({ - invitesRepository, - settingsService, - emailService, - }); - const maintenanceWindowService = new MaintenanceWindowService({ - monitorsRepository, - maintenanceWindowsRepository, - jobsRepository, - scheduler: worker, - }); - const monitorService = new MonitorService({ - scheduler: worker, - logger, - games, - monitorsRepository, - checksRepository, - geoChecksRepository, - monitorStatsRepository, - statusPagesRepository, - incidentsRepository, - }); - - const statusPageService = new StatusPageService(statusPagesRepository, settingsService); - - const services = { - settingsService, - db, - networkService, - emailService, - bufferService, - statusService, - worker, - userService, - checkService, - geoChecksService, - diagnosticService, - inviteService, - maintenanceWindowService, - monitorService, - incidentService, - logger, - notificationsService, - tagsService, - statusPageService, - notificationMessageBuilder, - - // Repositories - monitorsRepository, - checksRepository, - geoChecksRepository, - monitorStatsRepository, - statusPagesRepository, - usersRepository, - invitesRepository, - recoveryTokensRepository, - settingsRepository, - notificationsRepository, - tagsRepository, - incidentsRepository, - teamsRepository, - maintenanceWindowsRepository, - }; - - return services; -}; diff --git a/server/src/config/services.worker.ts b/server/src/config/services.worker.ts index e69de29bb..93233cd37 100644 --- a/server/src/config/services.worker.ts +++ b/server/src/config/services.worker.ts @@ -0,0 +1,151 @@ +import { Resolver } from "dns/promises"; +import axios from "axios"; +import got from "got"; +import ping from "ping"; +import Docker from "dockerode"; +import net from "net"; +import { GameDig } from "gamedig"; +import jmespath from "jmespath"; +import * as grpc from "@grpc/grpc-js"; +import * as protoLoader from "@grpc/proto-loader"; +import WebSocket from "ws"; + +import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; +import { SharedServices } from "@/config/services.shared.js"; +import { INetworkService, NetworkService } from "@/service/networkService.js"; +import { IBufferService, BufferService } from "@/service/bufferService.js"; +import { IStatusService, StatusService } from "@/service/statusService.js"; +import { IQueueWorker } from "@/worker/worker.interface.js"; +import { MonitorStatusPolicy } from "@/worker/worker.monitor-status-policy.js"; +import { WorkerHelper } from "@/worker/worker.helper.js"; +import { CheckProducer } from "@/worker/worker.check-producer.js"; +import { CheckEvaluator } from "@/worker/worker.check-evaluator.js"; +import { GeoChecksPipeline } from "@/worker/worker.check-pipeline.js"; +import { NotificationReactor } from "@/worker/reactors/reactor.notification.js"; +import { IncidentReactor } from "@/worker/reactors/reactor.incident.js"; +import { ReactorDispatcher } from "@/worker/reactors/reactor.dispatcher.js"; +import { DBQueueWorker } from "@/worker/worker.db-queue.js"; + +// Network providers +import { PingProvider } from "@/service/network/PingProvider.js"; +import { HttpProvider } from "@/service/network/HttpProvider.js"; +import { AdvancedMatcher } from "@/service/network/AdvancedMatcher.js"; +import { PageSpeedProvider } from "@/service/network/PageSpeedProvider.js"; +import { HardwareProvider } from "@/service/network/HardwareProvider.js"; +import { DockerProvider } from "@/service/network/DockerProvider.js"; +import { PortProvider } from "@/service/network/PortProvider.js"; +import { GameProvider } from "@/service/network/GameProvider.js"; +import { GrpcProvider } from "@/service/network/GrpcProvider.js"; +import { WebSocketProvider } from "@/service/network/WebSocketProvider.js"; +import { DNSProvider } from "@/service/network/DNSProvider.js"; +export interface WorkerServices { + worker: IQueueWorker; + networkService: INetworkService; + bufferService: IBufferService; + statusService: IStatusService; +} + +export const buildWorker = async (shared: SharedServices, envSettings: EnvConfig): Promise => { + const { + logger, + settingsService, + checkService, + geoChecksService, + notificationsService, + incidentService, + workerId, + jobsRepository, + queueWorkersRepository, + monitorsRepository, + checksRepository, + geoChecksRepository, + monitorStatsRepository, + incidentsRepository, + teamsRepository, + maintenanceWindowsRepository, + } = shared; + + // *********************** + // Network providers + // *********************** + const pingProvider = new PingProvider(ping); + const httpProvider = new HttpProvider(got, new AdvancedMatcher(jmespath)); + const pageSpeedProvider = new PageSpeedProvider(httpProvider, settingsService, logger); + const hardwareProvider = new HardwareProvider(httpProvider); + const dockerProvider = new DockerProvider(logger, Docker); + const portProvider = new PortProvider(net); + const gameProvider = new GameProvider(logger, GameDig); + const grpcProvider = new GrpcProvider(grpc, protoLoader); + const webSocketProvider = new WebSocketProvider(WebSocket); + const dnsProvider = new DNSProvider(() => new Resolver()); + + const networkService = new NetworkService(axios, logger, [ + pingProvider, + httpProvider, + pageSpeedProvider, + hardwareProvider, + dockerProvider, + portProvider, + gameProvider, + grpcProvider, + webSocketProvider, + dnsProvider, + ]); + + const bufferService = new BufferService(logger, checkService, geoChecksService, settingsService, jobsRepository); + const statusService = new StatusService(logger, monitorsRepository, monitorStatsRepository); + const monitorStatusPolicy = new MonitorStatusPolicy(); + + // *********************** + // Reactors and dispatcher + // Handles notifications and incidents + // *********************** + + const notificationReactor = new NotificationReactor(notificationsService); + const incidentReactor = new IncidentReactor(incidentService); + const reactorDispatcher = new ReactorDispatcher(logger, [notificationReactor, incidentReactor]); + + // *********************** + // Check producer/evaluator + // Handles creating and evaluatiog checks + // *********************** + const checkProducer = new CheckProducer(monitorsRepository, maintenanceWindowsRepository, checkService, networkService, bufferService, logger); + const checkEvaluator = new CheckEvaluator(statusService, monitorStatusPolicy); + const geoCheckPipeline = new GeoChecksPipeline(maintenanceWindowsRepository, geoChecksService, bufferService, logger); + + // *********************** + // Worker + // *********************** + + const workerHelper = new WorkerHelper( + logger, + checkService, + settingsService, + monitorsRepository, + jobsRepository, + teamsRepository, + monitorStatsRepository, + checksRepository, + incidentsRepository, + geoChecksRepository + ); + + const worker = await DBQueueWorker.create( + logger, + jobsRepository, + monitorsRepository, + checksRepository, + checkService, + checkProducer, + checkEvaluator, + geoCheckPipeline, + reactorDispatcher, + workerHelper, + queueWorkersRepository, + envSettings.queueMode, + envSettings.queuePrimaryProcesses, + workerId + ); + + return { worker, networkService, bufferService, statusService }; +}; diff --git a/server/src/domain/checks/check.model.ts b/server/src/domain/checks/check.model.ts index 82be6937c..c3fbc23ff 100644 --- a/server/src/domain/checks/check.model.ts +++ b/server/src/domain/checks/check.model.ts @@ -241,6 +241,7 @@ const CheckSchema = new Schema( }, errors: { type: [errorSchema], + suppressReservedKeysWarning: true, default: () => [], }, capture: { diff --git a/server/src/index.ts b/server/src/index.ts index 37d4e59d8..d121d7b25 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,4 +1,7 @@ -import { initializeServices } from "./config/services.js"; +import { buildShared } from "@/config/services.shared.js"; +import { buildApi } from "@/config/services.api.js"; +import { buildWorker } from "@/config/services.worker.js"; + import { initializeControllers } from "./config/controllers.js"; import { createApp } from "./app.js"; import { initShutdownListener } from "@/shutdown.js"; @@ -9,44 +12,83 @@ import fs from "fs"; import Logger, { ILogger } from "@/utils/logger.js"; import { SettingsService } from "@/domain/app-settings/app-settings.service.js"; - +import { JobScheduler } from "@/worker/worker.job-scheduler.js"; +import { IJobScheduler } from "@/worker/worker.interface.js"; const SERVICE_NAME = "Server"; let logger: ILogger; const startApp = async () => { - // Validate environment variables first - const env = validateEnv(); + // *********************** + // Basic setup + // *********************** - // Create settings service (env only — DB repository injected after connect) + const env = validateEnv(); const settingsService = new SettingsService(env); const envSettings = settingsService.loadSettings(); - // Create logger logger = new Logger({ envSettings }); + const { queueMode, queuePrimaryProcesses } = envSettings; - // Initialize services (connects DB, creates repositories, injects settingsRepository) - const services = await initializeServices({ logger, envSettings, settingsService }); + logger.info({ + message: `Queue mode: ${queueMode}`, + service: SERVICE_NAME, + method: "startApp", + }); - // If this is a worker instance, we're done. No need for express - if (envSettings.queueMode === "worker") { - logger.info({ - message: "Worker instance started. API will not be started", - service: SERVICE_NAME, - }); - initShutdownListener(null, services); + logger.info({ + message: `Process: ${queuePrimaryProcesses}`, + service: SERVICE_NAME, + method: "startApp", + }); + + // *********************** + // Build shared services + // *********************** + const shared = await buildShared({ logger, envSettings, settingsService }); + + // *********************** + // Worker node path, don't need API + // *********************** + if (queueMode === "worker") { + const { worker } = await buildWorker(shared, envSettings); + logger.info({ message: "Worker instance started. API will not be started", service: SERVICE_NAME }); + initShutdownListener(null, { worker, db: shared.db }); return; } - // FE path + // *********************** + // Primary node path + // *********************** + let scheduler: IJobScheduler; + + // *********************** + //Primary node processes jobs, need full worker + // *********************** + if (queuePrimaryProcesses === true) { + const { worker } = await buildWorker(shared, envSettings); + scheduler = worker; + } + // *********************** + // Primary node does not process jobs, only need scheduler + // *********************** + else { + scheduler = new JobScheduler(shared.jobsRepository, shared.queueWorkersRepository, shared.workerId); + } + + const services = buildApi(shared, scheduler); + + // *********************** + // FE assets always needed for primary node + // *********************** + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8")); const frontendPath = path.join(__dirname, "..", "public"); - // Initialize controllers const controllers = initializeControllers(services); const app = createApp({ - services, + apiServices: services, controllers, envSettings, frontendPath, diff --git a/server/src/shutdown.ts b/server/src/shutdown.ts index 38d1d5114..584cfea39 100644 --- a/server/src/shutdown.ts +++ b/server/src/shutdown.ts @@ -1,8 +1,14 @@ -import { InitializedServices } from "./config/services.js"; +import { IDb } from "@/db/db.interface.js"; import { logger } from "./utils/logger.js"; import type { Server } from "http"; +import { Mongoose } from "mongoose"; +import { IJobScheduler } from "@/worker/worker.interface.js"; -export const initShutdownListener = (server: Server | null, services: InitializedServices) => { +type ShutdownTargets = { + worker: IJobScheduler; + db: IDb; +}; +export const initShutdownListener = (server: Server | null, services: ShutdownTargets) => { const SERVICE_NAME = "Server"; let isShuttingDown = false;