From 602597abe27e9899adc89da360f496763f2d5cb3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 2 Jan 2026 23:28:36 +0000 Subject: [PATCH] jobqueue and stauts service --- server/src/init/services.ts | 7 +- .../monitors/IMonitorRepoistory.ts | 46 ++++++++- .../monitors/MongoMonitorRepository.ts | 93 +++++++++++++++++- .../src/services/business/MonitorService.ts | 96 ++++++++----------- .../services/infrastructure/JobGenerator.ts | 14 ++- .../src/services/infrastructure/JobQueue.ts | 48 ++++++---- .../services/infrastructure/NetworkService.ts | 43 +++++---- .../services/infrastructure/StatusService.ts | 47 +++++++-- 8 files changed, 270 insertions(+), 124 deletions(-) diff --git a/server/src/init/services.ts b/server/src/init/services.ts index f58773a48..524d9b5da 100644 --- a/server/src/init/services.ts +++ b/server/src/init/services.ts @@ -36,6 +36,8 @@ import { MeService } from "@/services/index.js"; import { MongoMonitorRepository } from "@/repositories/index.js"; export const initServices = async () => { + const monitorRepository = new MongoMonitorRepository(); + const checkService = new CheckService(); const inviteService = new InviteService(); const maintenanceService = new MaintenanceService(); @@ -46,7 +48,7 @@ export const initServices = async () => { const slackService = new SlackService(); const webhookService = new WebhookService(); const networkService = new NetworkService(got); - const statusService = new StatusService(); + const statusService = new StatusService(monitorRepository); const settingsService = new SettingsService(); const stripeService = new StripeService(); const billingService = new BillingService(); @@ -68,11 +70,10 @@ export const initServices = async () => { maintenanceService, statsAggregationService ); - const jobQueue = await JobQueue.create(jobGenerator); + const jobQueue = await JobQueue.create(jobGenerator, monitorRepository); const entitlementsProvider = EntitlementsFactory.create(); const authService = new AuthService(jobQueue, entitlementsProvider); const meService = new MeService(entitlementsProvider); - const monitorRepository = new MongoMonitorRepository(); const monitorService = new MonitorService(jobQueue, monitorRepository); const queueService = new QueueService(jobQueue); const teamService = new TeamService(jobQueue); diff --git a/server/src/repositories/monitors/IMonitorRepoistory.ts b/server/src/repositories/monitors/IMonitorRepoistory.ts index 702fd29ad..9ed3b2f36 100644 --- a/server/src/repositories/monitors/IMonitorRepoistory.ts +++ b/server/src/repositories/monitors/IMonitorRepoistory.ts @@ -4,6 +4,20 @@ import type { MonitorStatus, } from "@/types/domain/monitor.js"; +export type UpdateMonitorPatch = Partial< + Pick< + Monitor, + | "name" + | "secret" + | "interval" + | "rejectUnauthorized" + | "n" + | "thresholds" + | "notificationChannels" + | "status" + > +>; + export interface TeamQueryConfig { search: string; sortField: string; @@ -15,7 +29,16 @@ export interface TeamQueryConfig { } export interface IMonitorRepository { + // Create + create( + orgId: string, + userId: string, + currentTeamId: string, + monitorData: Monitor + ): Promise; + // Single fetch + findAll(): Promise; findById(monitorId: string, teamId: string): Promise; // Collection fetches @@ -34,8 +57,23 @@ export interface IMonitorRepository { findByOrgId(orgId: string): Promise; // Deletions - deleteById(id: string): Promise; - deleteByOrgId(orgId: string): Promise; -} + deleteById(monitorId: string, teamId: string): Promise; + deleteByOrgId(orgId: string): Promise; -export default IMonitorRepository; + // Updates + togglePauseById( + monitorId: string, + teamId: string, + userId: string + ): Promise; + + updateById( + monitorId: string, + teamId: string, + userId: string, + patch: UpdateMonitorPatch + ): Promise; + + // Counts + countByOrgId(orgId: string): Promise; +} diff --git a/server/src/repositories/monitors/MongoMonitorRepository.ts b/server/src/repositories/monitors/MongoMonitorRepository.ts index a889af178..69c5c3be5 100644 --- a/server/src/repositories/monitors/MongoMonitorRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorRepository.ts @@ -30,6 +30,27 @@ class MongoMonitorRepository implements IMonitorRepository { }; }; + // Create + create = async ( + orgId: string, + userId: string, + currentTeamId: string, + monitorData: MonitorEntity + ) => { + const monitorLiteral: Partial = { + ...monitorData, + orgId: new mongoose.Types.ObjectId(orgId), + teamId: new mongoose.Types.ObjectId(currentTeamId), + createdBy: new mongoose.Types.ObjectId(userId), + updatedBy: new mongoose.Types.ObjectId(userId), + notificationChannels: monitorData.notificationChannels?.map( + (id) => new mongoose.Types.ObjectId(id) + ), + }; + const monitor = await Monitor.create(monitorLiteral); + return this.toEntity(monitor); + }; + // Single fetch findById = async (monitorId: string, teamId: string) => { const monitor = await Monitor.findOne({ _id: monitorId, teamId }); @@ -38,6 +59,11 @@ class MongoMonitorRepository implements IMonitorRepository { }; // Collection fetches + findAll = async () => { + const monitors = await Monitor.find(); + return monitors.map(this.toEntity); + }; + findByTeamId = async (teamId: string) => { const monitors = await Monitor.find({ teamId }); return monitors.map(this.toEntity); @@ -81,8 +107,10 @@ class MongoMonitorRepository implements IMonitorRepository { return monitors.map(this.toEntity); }; + findByOrgId = async (orgId: string) => { - return []; + const monitors = await Monitor.find({ orgId }); + return monitors.map(this.toEntity); }; findMonitorCountsByTeamId = async (teamId: string) => { @@ -115,12 +143,69 @@ class MongoMonitorRepository implements IMonitorRepository { }; // Deletions - deleteById = async (id: string) => { - return false; + deleteById = async (monitorId: string, teamId: string) => { + await Monitor.deleteOne({ _id: monitorId, teamId }); + return true; }; deleteByOrgId = async (orgId: string) => { - return 0; + const deleted = await Monitor.deleteMany({ orgId }); + return deleted.acknowledged; }; + + togglePauseById = async ( + monitorId: string, + teamId: string, + userId: string + ) => { + const monitor = await Monitor.findOneAndUpdate( + { _id: monitorId, teamId }, + [ + { + $set: { + status: { + $cond: { + if: { $eq: ["$status", "paused"] }, + then: "resuming", + else: "paused", + }, + }, + updatedBy: userId, + updatedAt: new Date(), + }, + }, + ], + { new: true } + ); + if (!monitor) return null; + return this.toEntity(monitor); + }; + + // Update + updateById = async ( + monitorId: string, + teamId: string, + userId: string, + patch: Partial + ) => { + const updatedMonitor = await Monitor.findOneAndUpdate( + { _id: monitorId, teamId }, + { + $set: { + ...patch, + updatedAt: new Date(), + updatedBy: userId, + }, + }, + { new: true, runValidators: true } + ); + if (!updatedMonitor) return null; + return this.toEntity(updatedMonitor); + }; + + // Counts + countByOrgId(orgId: string): Promise { + return Monitor.countDocuments({ orgId }); + } } export default MongoMonitorRepository; diff --git a/server/src/services/business/MonitorService.ts b/server/src/services/business/MonitorService.ts index 384e6bc7c..43af4a42c 100644 --- a/server/src/services/business/MonitorService.ts +++ b/server/src/services/business/MonitorService.ts @@ -44,8 +44,8 @@ export interface IMonitorService { orgId: string, userId: string, currentTeamId: string, - monitorData: IMonitor - ) => Promise; + monitorData: MonitorEntity + ) => Promise; getAll: (teamId: string) => Promise; getAllEmbedChecks: ( teamId: string, @@ -73,13 +73,13 @@ export interface IMonitorService { userId: string, teamId: string, monitorId: string - ) => Promise; + ) => Promise; update: ( userId: string, teamId: string, monitorId: string, - updateData: Partial - ) => Promise; + updateData: Partial + ) => Promise; delete: (teamId: string, monitorId: string) => Promise; deleteAllInOrg: (orgId: string) => Promise; export: (teamId: string) => Promise>>; @@ -106,19 +106,16 @@ class MonitorService implements IMonitorService { orgId: string, userId: string, currentTeamId: string, - monitorData: IMonitor + monitorData: MonitorEntity ) => { - const monitorLiteral: Partial = { - ...monitorData, - orgId: new mongoose.Types.ObjectId(orgId), - teamId: new mongoose.Types.ObjectId(currentTeamId), - createdBy: new mongoose.Types.ObjectId(userId), - updatedBy: new mongoose.Types.ObjectId(userId), - }; - - const monitor = await Monitor.create(monitorLiteral); + const monitor = await this.monitorRepository.create( + orgId, + userId, + currentTeamId, + monitorData + ); await MonitorStats.create({ - monitorId: monitor._id, + monitorId: monitor.id, currentStreakStartedAt: Date.now(), }); await this.jobQueue.addJob(monitor); @@ -736,24 +733,10 @@ class MonitorService implements IMonitorService { }; async togglePause(userId: string, teamId: string, id: string) { - const updatedMonitor = await Monitor.findOneAndUpdate( - { _id: id, teamId }, - [ - { - $set: { - status: { - $cond: { - if: { $eq: ["$status", "paused"] }, - then: "resuming", - else: "paused", - }, - }, - updatedBy: userId, - updatedAt: new Date(), - }, - }, - ], - { new: true } + const updatedMonitor = await this.monitorRepository.togglePauseById( + id, + teamId, + userId ); if (!updatedMonitor) { @@ -774,9 +757,9 @@ class MonitorService implements IMonitorService { userId: string, teamId: string, monitorId: string, - updateData: Partial + updateData: Partial ) { - const allowedFields: (keyof IMonitor)[] = [ + const allowedFields: (keyof MonitorEntity)[] = [ "name", "secret", "interval", @@ -785,7 +768,7 @@ class MonitorService implements IMonitorService { "notificationChannels", "thresholds", ]; - const safeUpdate: Partial = {}; + const safeUpdate: Partial = {}; for (const field of allowedFields) { if (updateData[field] !== undefined) { @@ -793,16 +776,11 @@ class MonitorService implements IMonitorService { } } - const updatedMonitor = await Monitor.findOneAndUpdate( - { _id: monitorId, teamId }, - { - $set: { - ...safeUpdate, - updatedAt: new Date(), - updatedBy: userId, - }, - }, - { new: true, runValidators: true } + const updatedMonitor = await this.monitorRepository.updateById( + monitorId, + teamId, + userId, + safeUpdate ); if (!updatedMonitor) { @@ -813,7 +791,7 @@ class MonitorService implements IMonitorService { } async delete(teamId: string, monitorId: string) { - const monitor = await Monitor.findOne({ _id: monitorId, teamId }); + const monitor = await this.monitorRepository.findById(monitorId, teamId); if (!monitor) { throw new ApiError("Monitor not found", 404); } @@ -821,23 +799,29 @@ class MonitorService implements IMonitorService { if (!deleted) { throw new ApiError("Failed to delete monitor job from queue", 500); } - await monitor.deleteOne(); + await this.monitorRepository.deleteById(monitorId, teamId); return true; } async deleteAllInOrg(orgId: string) { - const montiors = await Monitor.find({ orgId }); + const montiors = await this.monitorRepository.findByOrgId(orgId); for (const monitor of montiors) { await this.jobQueue.deleteJob(monitor); } - const deleted = await Monitor.deleteMany({ orgId }); - return deleted.acknowledged; + return await this.monitorRepository.deleteByOrgId(orgId); } export = async (teamId: string) => { - return Monitor.find({ teamId }) - .select("name url port type interval n secret -_id") - .lean(); + const monitor = await this.monitorRepository.findByTeamId(teamId); + return monitor.map((m) => ({ + name: m.name, + url: m.url, + port: m.port, + type: m.type, + interval: m.interval, + n: m.n, + secret: m.secret, + })); }; import = async ( @@ -852,7 +836,7 @@ class MonitorService implements IMonitorService { let canImport = 0; try { // Get current count - const monitorCount = await Monitor.countDocuments({ orgId }); + const monitorCount = await this.monitorRepository.countByOrgId(orgId); // Check limits, return NO_CAPACITY if at limit if (monitorCount >= entitlements.monitorsMax) { diff --git a/server/src/services/infrastructure/JobGenerator.ts b/server/src/services/infrastructure/JobGenerator.ts index 11ebb32e6..8b81d6034 100644 --- a/server/src/services/infrastructure/JobGenerator.ts +++ b/server/src/services/infrastructure/JobGenerator.ts @@ -1,4 +1,4 @@ -import { IMonitor, ICheck } from "@/db/models/index.js"; +import { IMonitor } from "@/db/models/index.js"; import { INetworkService } from "./NetworkService.js"; import { ICheckService } from "../business/CheckService.js"; import { IMonitorStatsService } from "../business/MonitorStatsService.js"; @@ -6,17 +6,15 @@ import { IStatusService } from "./StatusService.js"; import { INotificationService } from "./NotificationService.js"; import { IMaintenanceService } from "../business/MaintenanceService.js"; import { IIncidentService } from "../business/IncidentService.js"; -import StatsAggregationService, { - IStatsAggregationService, -} from "@/services/business/StatsAggregationService.js"; -import { StatsHourly } from "@/db/models/index.js"; +import { IStatsAggregationService } from "@/services/business/StatsAggregationService.js"; import ApiError from "@/utils/ApiError.js"; import { getChildLogger } from "@/logger/Logger.js"; +import type { Monitor as MonitorEntity } from "@/types/domain/index.js"; const SERVICE_NAME = "JobGenerator"; const logger = getChildLogger(SERVICE_NAME); export interface IJobGenerator { - generateJob: () => (Monitor: IMonitor) => Promise; + generateJob: () => (monitor: MonitorEntity) => Promise; generateCleanupJob: () => () => Promise; } @@ -53,9 +51,9 @@ class JobGenerator implements IJobGenerator { } generateJob = () => { - return async (monitor: IMonitor) => { + return async (monitor: MonitorEntity) => { try { - const monitorId = monitor._id.toString(); + const monitorId = monitor.id.toString(); if (!monitorId) { throw new ApiError("No monitorID for creating job", 400); } diff --git a/server/src/services/infrastructure/JobQueue.ts b/server/src/services/infrastructure/JobQueue.ts index 57dd19738..4403f5b95 100644 --- a/server/src/services/infrastructure/JobQueue.ts +++ b/server/src/services/infrastructure/JobQueue.ts @@ -1,9 +1,10 @@ import { IJob } from "super-simple-scheduler/dist/job/job.js"; import { Monitor, IMonitor } from "@/db/models/index.js"; +import type { Monitor as MonitorEntity } from "@/types/domain/index.js"; import Scheduler from "super-simple-scheduler"; import { IJobGenerator } from "./JobGenerator.js"; import { getChildLogger } from "@/logger/Logger.js"; -import { config } from "@/config/index.js"; +import { IMonitorRepository } from "@/repositories/index.js"; const SERVICE_NAME = "JobQueue"; const logger = getChildLogger(SERVICE_NAME); @@ -29,11 +30,11 @@ export interface IJobData extends IJob { export interface IJobQueue { init: () => Promise; - addJob: (monitor: IMonitor) => Promise; - pauseJob: (monitor: IMonitor) => Promise; - resumeJob: (monitor: IMonitor) => Promise; - updateJob: (monitor: IMonitor) => Promise; - deleteJob: (monitor: IMonitor) => Promise; + addJob: (monitor: MonitorEntity) => Promise; + pauseJob: (monitor: MonitorEntity) => Promise; + resumeJob: (monitor: MonitorEntity) => Promise; + updateJob: (monitor: MonitorEntity) => Promise; + deleteJob: (monitor: MonitorEntity) => Promise; getMetrics: () => Promise; getJobs: () => Promise; flush: () => Promise; @@ -45,16 +46,21 @@ export default class JobQueue implements IJobQueue { private scheduler: Scheduler; private static instance: JobQueue | null = null; private jobGenerator: any; - constructor() { + private monitorRepository: IMonitorRepository; + constructor(monitorRepository: IMonitorRepository) { this.SERVICE_NAME = SERVICE_NAME; this.scheduler = new Scheduler({ logLevel: "debug", }); + this.monitorRepository = monitorRepository; } - static async create(jobGenerator: IJobGenerator) { + static async create( + jobGenerator: IJobGenerator, + monitorRepository: IMonitorRepository + ) { if (!JobQueue.instance) { - const instance = new JobQueue(); + const instance = new JobQueue(monitorRepository); instance.jobGenerator = jobGenerator; await instance.init(); JobQueue.instance = instance; @@ -107,12 +113,12 @@ export default class JobQueue implements IJobQueue { logger.warn("Initial stats aggregation run failed", e as any); } - const monitors = await Monitor.find(); + const monitors = await this.monitorRepository.findAll(); for (const monitor of monitors) { const randomOffset = 1000 + Math.random() * 1000; setTimeout(async () => { - const exists = await Monitor.exists({ _id: monitor._id }); + const exists = await Monitor.exists({ _id: monitor.id }); if (exists) { this.addJob(monitor); } @@ -126,10 +132,10 @@ export default class JobQueue implements IJobQueue { } }; - addJob = async (monitor: IMonitor) => { + addJob = async (monitor: MonitorEntity) => { try { return await this.scheduler?.addJob({ - id: monitor._id.toString(), + id: monitor.id, template: "monitor-job", repeat: monitor.interval, active: monitor.status !== "paused", @@ -141,27 +147,27 @@ export default class JobQueue implements IJobQueue { } }; - pauseJob = async (monitor: IMonitor) => { + pauseJob = async (monitor: MonitorEntity) => { try { - return await this.scheduler?.pauseJob(monitor._id.toString()); + return await this.scheduler?.pauseJob(monitor.id); } catch (error) { logger.error(error); return false; } }; - resumeJob = async (monitor: IMonitor) => { + resumeJob = async (monitor: MonitorEntity) => { try { - return await this.scheduler.resumeJob(monitor._id.toString()); + return await this.scheduler.resumeJob(monitor.id); } catch (error) { logger.error(error); return false; } }; - updateJob = async (monitor: IMonitor) => { + updateJob = async (monitor: MonitorEntity) => { try { - return await this.scheduler.updateJob(monitor._id.toString(), { + return await this.scheduler.updateJob(monitor.id, { repeat: monitor.interval, data: monitor, }); @@ -171,9 +177,9 @@ export default class JobQueue implements IJobQueue { } }; - deleteJob = async (monitor: IMonitor) => { + deleteJob = async (monitor: MonitorEntity) => { try { - this.scheduler?.removeJob(monitor._id.toString()); + this.scheduler?.removeJob(monitor.id); return true; } catch (error) { logger.error(error); diff --git a/server/src/services/infrastructure/NetworkService.ts b/server/src/services/infrastructure/NetworkService.ts index 28f1f106f..b797d6950 100644 --- a/server/src/services/infrastructure/NetworkService.ts +++ b/server/src/services/infrastructure/NetworkService.ts @@ -16,16 +16,17 @@ import { config } from "@/config/index.js"; import CacheableLookup from "cacheable-lookup"; import { getChildLogger } from "@/logger/Logger.js"; import type { IDockerContainerSummary } from "@/db/models/index.js"; +import type { Monitor as MonitorEntity } from "@/types/domain/monitor.js"; const SERVICE_NAME = "NetworkService"; const logger = getChildLogger(SERVICE_NAME); export interface INetworkService { - requestHttp: (monitor: IMonitor) => Promise; - requestInfrastructure: (monitor: IMonitor) => Promise; - requestStatus: (monitor: IMonitor) => Promise; - requestPagespeed: (monitor: IMonitor) => Promise; - requestPing: (monitor: IMonitor) => Promise; - requestPort: (monitor: IMonitor) => Promise; + requestHttp: (monitor: MonitorEntity) => Promise; + requestInfrastructure: (monitor: MonitorEntity) => Promise; + requestStatus: (monitor: MonitorEntity) => Promise; + requestPagespeed: (monitor: MonitorEntity) => Promise; + requestPing: (monitor: MonitorEntity) => Promise; + requestPort: (monitor: MonitorEntity) => Promise; } export interface ICapturePayload { @@ -92,7 +93,7 @@ class NetworkService implements INetworkService { } private buildStatusResponse = ( - monitor: IMonitor, + monitor: MonitorEntity, response: Response | null, certificateExpiryOrError?: Date | null | any, maybeError?: any | null @@ -110,8 +111,8 @@ class NetworkService implements INetworkService { } if (error) { const statusResponse: StatusResponse = { - monitorId: monitor._id.toString(), - teamId: monitor.teamId.toString(), + monitorId: monitor.id, + teamId: monitor.teamId, type: monitor.type, status: "down" as MonitorStatus, code: this.NETWORK_ERROR, @@ -130,8 +131,8 @@ class NetworkService implements INetworkService { } const statusResponse: StatusResponse = { - monitorId: monitor._id.toString(), - teamId: monitor.teamId.toString(), + monitorId: monitor.id, + teamId: monitor.teamId, type: monitor.type, code: response?.statusCode || this.NETWORK_ERROR, status: response?.ok === true ? "up" : "down", @@ -144,7 +145,7 @@ class NetworkService implements INetworkService { return statusResponse; }; - requestHttp = async (monitor: IMonitor) => { + requestHttp = async (monitor: MonitorEntity) => { try { const url = monitor.url; if (!url) { @@ -190,7 +191,7 @@ class NetworkService implements INetworkService { } }; - requestInfrastructure = async (monitor: IMonitor) => { + requestInfrastructure = async (monitor: MonitorEntity) => { const url = monitor.url; if (!url) { throw new Error("No URL provided"); @@ -225,7 +226,7 @@ class NetworkService implements INetworkService { return statusResponse; }; - requestPagespeed = async (monitor: IMonitor) => { + requestPagespeed = async (monitor: MonitorEntity) => { const apiKey = config.PAGESPEED_API_KEY; if (!apiKey) { throw new Error("No API key provided for pagespeed monitor"); @@ -265,7 +266,7 @@ class NetworkService implements INetworkService { } }; - requestPing = async (monitor: IMonitor) => { + requestPing = async (monitor: MonitorEntity) => { const response = await ping.promise.probe(monitor.url); const status = response?.alive === true ? "up" : "down"; @@ -276,8 +277,8 @@ class NetworkService implements INetworkService { const responseTime = Number.isFinite(rawTime) ? rawTime : 0; return { - monitorId: monitor._id.toString(), - teamId: monitor.teamId.toString(), + monitorId: monitor.id, + teamId: monitor.teamId, type: monitor.type, status: status as MonitorStatus, message: "Ping successful", @@ -286,10 +287,10 @@ class NetworkService implements INetworkService { }; }; - requestPort = async (monitor: IMonitor) => { + requestPort = async (monitor: MonitorEntity) => { const response = { - monitorId: monitor._id.toString(), - teamId: monitor.teamId.toString(), + monitorId: monitor.id, + teamId: monitor.teamId, type: monitor.type, status: "down" as MonitorStatus, message: "Port check failed", @@ -358,7 +359,7 @@ class NetworkService implements INetworkService { } }; - requestStatus = async (monitor: IMonitor) => { + requestStatus = async (monitor: MonitorEntity) => { switch (monitor?.type) { case "http": return await this.requestHttp(monitor); // uses GOT diff --git a/server/src/services/infrastructure/StatusService.ts b/server/src/services/infrastructure/StatusService.ts index 1bebefe3f..7dfc2bd9f 100644 --- a/server/src/services/infrastructure/StatusService.ts +++ b/server/src/services/infrastructure/StatusService.ts @@ -5,6 +5,9 @@ import { ICheck, } from "@/db/models/index.js"; import { StatusResponse } from "./NetworkService.js"; +import { IMonitorRepository } from "@/repositories/index.js"; +import type { Monitor as MonitorEntity } from "@/types/domain/index.js"; +import ApiError from "@/utils/ApiError.js"; // Using compact latestStatuses on Monitor for transition checks const emptyMetrics = (): ThresholdEvaluationResult["metrics"] => ({ @@ -33,9 +36,10 @@ export interface ThresholdEvaluationResult { const SERVICE_NAME = "StatusService"; const MAX_LATEST_CHECKS = 25; + export interface IStatusService { updateMonitorStatus: ( - monitor: IMonitor, + monitor: MonitorEntity, status: StatusResponse ) => Promise; @@ -56,18 +60,20 @@ export interface IStatusService { } export type StatusChangeResult = [ - updatedMonitor: IMonitor, + updatedMonitor: MonitorEntity, statusChanged: boolean ]; class StatusService implements IStatusService { public SERVICE_NAME: string; - constructor() { + private monitorRepository: IMonitorRepository; + constructor(monitorRepository: IMonitorRepository) { this.SERVICE_NAME = SERVICE_NAME; + this.monitorRepository = monitorRepository; } updateMonitorStatus = async ( - monitor: IMonitor, + monitor: MonitorEntity, statusResponse: StatusResponse ): Promise => { const newStatus = statusResponse.status; @@ -84,20 +90,47 @@ class StatusService implements IStatusService { monitor.status = newStatus; const lenAfter = monitor.latestStatuses.length; const thresholdReachedNow = lenAfter >= monitor.n && prevLen < monitor.n; - return [await monitor.save(), thresholdReachedNow]; + const updated = await this.monitorRepository.updateById( + monitor.id, + monitor.teamId, + monitor.updatedBy, + monitor + ); + if (!updated) { + throw new ApiError("Failed to update monitor status", 500); + } + return [updated, thresholdReachedNow]; } const { n } = monitor; const latestN = monitor.latestStatuses.slice(-n); if (latestN.length < n) { - return [await monitor.save(), false]; + const updated = await this.monitorRepository.updateById( + monitor.id, + monitor.teamId, + monitor.updatedBy, + monitor + ); + if (!updated) { + throw new ApiError("Failed to update monitor status", 500); + } + return [updated, false]; } const allDifferent = latestN.every((s) => s !== monitor.status); if (allDifferent && monitor.status !== newStatus) { monitor.status = newStatus; } - return [await monitor.save(), allDifferent]; + const updated = await this.monitorRepository.updateById( + monitor.id, + monitor.teamId, + monitor.updatedBy, + monitor + ); + if (!updated) { + throw new ApiError("Failed to update monitor status", 500); + } + return [updated, allDifferent]; }; calculateAvgResponseTime = (