remove circular dep

This commit is contained in:
Alex Holliday
2026-02-24 19:19:32 +00:00
parent 5a74411ea2
commit 432d1d8ccf
11 changed files with 139 additions and 153 deletions
+8 -39
View File
@@ -91,7 +91,7 @@ export type InitializedServices = {
maintenanceWindowService: any;
monitorService: any;
incidentService: any;
logger: any;
logger: ILogger;
notificationsService: INotificationsService;
statusPageService: IStatusPageService;
notificationMessageBuilder: INotificationMessageBuilder;
@@ -143,52 +143,20 @@ export const initializeServices = async ({
const teamsRepository = new MongoTeamsRepository();
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
const networkService = new NetworkService({
axios,
got,
https,
jmespath,
GameDig,
ping,
logger,
http,
Docker,
net,
settingsService,
});
const networkService = new NetworkService(axios, got, https, jmespath, GameDig, ping, logger, Docker, net, settingsService);
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
const notificationMessageBuilder = new NotificationMessageBuilder();
const incidentService = new IncidentService({
logger,
incidentsRepository,
monitorsRepository,
usersRepository,
notificationMessageBuilder,
});
const incidentService = new IncidentService(logger, incidentsRepository, monitorsRepository, usersRepository, notificationMessageBuilder);
const checkService = new CheckService({
monitorsRepository,
logger,
checksRepository,
});
const checkService = new CheckService(monitorsRepository, logger, checksRepository);
const globalPingService = new GlobalPingService({ logger });
const globalPingService = new GlobalPingService(logger);
// Create geoChecksService with circular dependency workaround
const geoChecksService = new GeoChecksService({
logger,
geoChecksRepository,
globalPingService,
bufferService: null as any,
settingsService,
});
const geoChecksService = new GeoChecksService(logger, geoChecksRepository, globalPingService);
const bufferService = new BufferService({ logger, checkService, geoChecksService, settingsService });
// Set bufferService reference
(geoChecksService as any).bufferService = bufferService;
const bufferService = new BufferService(logger, checkService, geoChecksService, settingsService);
const statusService = new StatusService(logger, bufferService, monitorsRepository, monitorStatsRepository, checksRepository);
@@ -269,6 +237,7 @@ export const initializeServices = async ({
games,
monitorsRepository,
checksRepository,
geoChecksRepository,
monitorStatsRepository,
statusPagesRepository,
incidentsRepository,
@@ -134,6 +134,31 @@ class MonitorController {
}
};
getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
try {
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
const monitorId = requireString(req?.params?.monitorId, "Monitor ID");
const dateRange = requireString(req?.query?.dateRange, "dateRange");
const teamId = requireTeamId(req?.user?.teamId);
const data = await this.monitorService.getGeoChecksByMonitorId({
teamId,
monitorId,
dateRange,
});
return res.status(200).json({
success: true,
msg: "Geo checks retrieved successfully",
data,
});
} catch (error) {
next(error);
}
};
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
+3
View File
@@ -30,6 +30,9 @@ class MonitorRoutes {
// PageSpeed routes
this.router.get("/pagespeed/details/:monitorId", this.monitorController.getPageSpeedDetailsById);
// Geo checks routes
this.router.get("/:monitorId/geo-checks", this.monitorController.getGeoChecksByMonitorId);
// General monitor routes
this.router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), this.monitorController.pauseMonitor);
+3 -11
View File
@@ -1,10 +1,10 @@
import { requireTeamId } from "@/controllers/controllerUtils.js";
import { Types } from "mongoose";
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
import type { MonitorType, MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
import type { MonitorStatusResponse, CheckErrorInfo, Check } from "@/types/index.js";
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import { ParseBoolean } from "@/utils/utils.js";
import { ILogger } from "@/utils/logger.js";
const SERVICE_NAME = "checkService";
@@ -14,15 +14,7 @@ class CheckService {
private monitorsRepository: IMonitorsRepository;
private checksRepository: IChecksRepository;
private logger: any;
constructor({
monitorsRepository,
logger,
checksRepository,
}: {
monitorsRepository: IMonitorsRepository;
logger: any;
checksRepository: IChecksRepository;
}) {
constructor(monitorsRepository: IMonitorsRepository, logger: ILogger, checksRepository: IChecksRepository) {
this.monitorsRepository = monitorsRepository;
this.logger = logger;
this.checksRepository = checksRepository;
+26 -39
View File
@@ -3,42 +3,28 @@ import type { GeoCheckResult } from "@/types/geoCheck.js";
import { Types } from "mongoose";
import type { IGeoChecksRepository } from "@/repositories/index.js";
import type { IGlobalPingService } from "@/service/infrastructure/globalPingService.js";
import type { IBufferService } from "@/service/infrastructure/bufferService.js";
import type { ILogger } from "@/utils/logger.js";
import type { ISettingsService } from "@/service/system/settingsService.js";
const SERVICE_NAME = "GeoChecksService";
export interface IGeoChecksService {
readonly serviceName: string;
executeGeoCheck(monitor: Monitor): Promise<void>;
buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null>;
createGeoChecks(geoChecks: GeoCheck[]): Promise<GeoCheck[]>;
}
class GeoChecksService implements IGeoChecksService {
static SERVICE_NAME = SERVICE_NAME;
private logger: any;
private logger: ILogger;
private geoChecksRepository: IGeoChecksRepository;
private globalPingService: IGlobalPingService;
private bufferService: IBufferService;
private TTL_DAYS: number;
constructor({
logger,
geoChecksRepository,
globalPingService,
bufferService,
settingsService,
}: {
logger: any;
geoChecksRepository: IGeoChecksRepository;
globalPingService: IGlobalPingService;
bufferService: IBufferService;
settingsService: any;
}) {
constructor(logger: ILogger, geoChecksRepository: IGeoChecksRepository, globalPingService: IGlobalPingService) {
this.logger = logger;
this.geoChecksRepository = geoChecksRepository;
this.globalPingService = globalPingService;
this.bufferService = bufferService;
this.TTL_DAYS = settingsService.getSettings().checksTTL || 90;
}
get serviceName() {
@@ -46,31 +32,31 @@ class GeoChecksService implements IGeoChecksService {
}
/**
* Execute a geo-distributed check for a monitor
* Build a geo-distributed check for a monitor
* 1. Create measurement request with GlobalPing API
* 2. Poll for results (with 30s timeout)
* 3. Transform and save results to buffer
* 3. Transform and return GeoCheck document
*/
async executeGeoCheck(monitor: Monitor): Promise<void> {
async buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null> {
try {
if (!monitor.url) {
this.logger.warn({
message: "Monitor missing URL for geo check",
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id },
});
return;
return null;
}
if (!monitor.geoCheckLocations || monitor.geoCheckLocations.length === 0) {
this.logger.warn({
message: "Monitor missing geo check locations",
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id },
});
return;
return null;
}
// Step 1: Create measurement request
@@ -81,10 +67,10 @@ class GeoChecksService implements IGeoChecksService {
this.logger.debug({
message: "Skipping geo check due to API unavailability",
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id },
});
return;
return null;
}
// Step 2: Poll for results
@@ -95,38 +81,39 @@ class GeoChecksService implements IGeoChecksService {
this.logger.debug({
message: "No successful geo check results",
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id, measurementId },
});
return;
return null;
}
// Step 3: Build GeoCheck document
const geoCheck = this.buildGeoCheck(monitor, results);
// Step 4: Add to buffer for batched insertion
this.bufferService.addGeoCheckToBuffer(geoCheck);
const geoCheck = this.createGeoCheckDocument(monitor, results);
this.logger.debug({
message: `Geo check completed for monitor ${monitor.id}`,
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id, resultsCount: results.length },
});
return geoCheck;
} catch (error: any) {
this.logger.error({
message: "Error executing geo check",
service: SERVICE_NAME,
method: "executeGeoCheck",
method: "buildGeoCheck",
details: { monitorId: monitor.id, error: error.message },
stack: error.stack,
});
return null;
}
}
private buildGeoCheck(monitor: Monitor, results: GeoCheckResult[]): GeoCheck {
private createGeoCheckDocument(monitor: Monitor, results: GeoCheckResult[]): GeoCheck {
const now = new Date();
const expiryDate = new Date(now.getTime() + this.TTL_DAYS * 24 * 60 * 60 * 1000);
const ttl = 90 * 24 * 60 * 60 * 1000; // 90 days in ms
const expiryDate = new Date(now.getTime() + ttl);
return {
id: new Types.ObjectId().toString(),
+14 -19
View File
@@ -7,6 +7,7 @@ import type { IIncidentsRepository, IMonitorsRepository, IUsersRepository } from
import type { Incident } from "@/types/index.js";
import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
import type { INotificationMessageBuilder } from "@/service/infrastructure/notificationMessageBuilder.js";
import type { ILogger } from "@/utils/logger.js";
const dateRangeLookup: Record<string, Date | undefined> = {
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
@@ -20,25 +21,19 @@ const dateRangeLookup: Record<string, Date | undefined> = {
class IncidentService {
static SERVICE_NAME = SERVICE_NAME;
private logger: any;
private logger: ILogger;
private incidentsRepository: IIncidentsRepository;
private monitorsRepository: IMonitorsRepository;
private usersRepository: IUsersRepository;
private notificationMessageBuilder: INotificationMessageBuilder;
constructor({
logger,
incidentsRepository,
monitorsRepository,
usersRepository,
notificationMessageBuilder,
}: {
logger: any;
incidentsRepository: IIncidentsRepository;
monitorsRepository: IMonitorsRepository;
usersRepository: IUsersRepository;
notificationMessageBuilder: INotificationMessageBuilder;
}) {
constructor(
logger: ILogger,
incidentsRepository: IIncidentsRepository,
monitorsRepository: IMonitorsRepository,
usersRepository: IUsersRepository,
notificationMessageBuilder: INotificationMessageBuilder
) {
this.logger = logger;
this.incidentsRepository = incidentsRepository;
this.monitorsRepository = monitorsRepository;
@@ -151,7 +146,7 @@ class IncidentService {
service: SERVICE_NAME,
method: "resolveIncidentManually",
message: `Incident manually resolved by user`,
details: resolvedIncident.id,
details: { incidentId: resolvedIncident.id },
});
return resolvedIncident;
@@ -160,7 +155,7 @@ class IncidentService {
service: SERVICE_NAME,
method: "resolveIncident",
message: error.message,
details: incidentId,
details: { id: incidentId },
stack: error.stack,
});
throw error;
@@ -207,7 +202,7 @@ class IncidentService {
service: SERVICE_NAME,
method: "getIncidentsByTeam",
message: error.message,
details: teamId,
details: { teamId },
stack: error.stack,
});
throw error;
@@ -229,7 +224,7 @@ class IncidentService {
service: SERVICE_NAME,
method: "getIncidentSummary",
message: error.message,
details: teamId,
details: { teamId },
stack: error.stack,
});
throw error;
@@ -250,7 +245,7 @@ class IncidentService {
service: SERVICE_NAME,
method: "getIncidentById",
message: error.message,
details: incidentId,
details: { incidentId },
stack: error.stack,
});
throw error;
@@ -10,6 +10,7 @@ import type {
} from "@/types/monitor.js";
import type {
IChecksRepository,
IGeoChecksRepository,
IIncidentsRepository,
IMonitorsRepository,
IMonitorStatsRepository,
@@ -36,6 +37,7 @@ export interface IMonitorService {
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string; normalize?: boolean }): Promise<UptimeDetailsResult>;
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<HardwareDetailsResult>;
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<PageSpeedDetailsResult>;
getGeoChecksByMonitorId(args: { teamId: string; monitorId: string; dateRange: string }): Promise<any>;
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
getMonitorsByTeamId(args: {
teamId: string;
@@ -84,6 +86,7 @@ export class MonitorService implements IMonitorService {
private games: any;
private monitorsRepository: IMonitorsRepository;
private checksRepository: IChecksRepository;
private geoChecksRepository: IGeoChecksRepository;
private monitorStatsRepository: IMonitorStatsRepository;
private statusPagesRepository: IStatusPagesRepository;
private incidentsRepository: IIncidentsRepository;
@@ -95,6 +98,7 @@ export class MonitorService implements IMonitorService {
games,
monitorsRepository,
checksRepository,
geoChecksRepository,
monitorStatsRepository,
statusPagesRepository,
incidentsRepository,
@@ -105,6 +109,7 @@ export class MonitorService implements IMonitorService {
games: any;
monitorsRepository: IMonitorsRepository;
checksRepository: IChecksRepository;
geoChecksRepository: IGeoChecksRepository;
monitorStatsRepository: IMonitorStatsRepository;
statusPagesRepository: IStatusPagesRepository;
incidentsRepository: IIncidentsRepository;
@@ -115,6 +120,7 @@ export class MonitorService implements IMonitorService {
this.games = games;
this.monitorsRepository = monitorsRepository;
this.checksRepository = checksRepository;
this.geoChecksRepository = geoChecksRepository;
this.monitorStatsRepository = monitorStatsRepository;
this.statusPagesRepository = statusPagesRepository;
this.incidentsRepository = incidentsRepository;
@@ -315,6 +321,24 @@ export class MonitorService implements IMonitorService {
monitorStats,
};
};
getGeoChecksByMonitorId = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise<any> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
if (!monitor) {
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
}
if (monitor.type !== "http" || !monitor.geoCheckEnabled) {
return { groupedGeoChecks: [] };
}
const rangeKey = (dateRange as DateRangeKey) ?? "recent";
const { start, end } = this.getDateRange(rangeKey);
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(monitor.id, start, end, this.getDateFormat(rangeKey));
return { groupedGeoChecks };
};
getMonitorById = async ({ teamId, monitorId }: { teamId: string; monitorId: string }): Promise<Monitor> => {
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
return monitor;
@@ -306,8 +306,19 @@ class SuperSimpleQueueHelper {
return;
}
// Step 3: Execute geo check (handles API calls, polling, and saving)
await this.geoChecksService.executeGeoCheck(monitor);
// Step 3: Build geo check (handles API calls and polling)
const geoCheck = await this.geoChecksService.buildGeoCheck(monitor);
if (!geoCheck) {
this.logger.warn({
message: `No geo check could be built for monitor ${monitorId}`,
service: SERVICE_NAME,
method: "getGeoCheckJob",
});
return;
}
// Step 4: Add geo check to buffer
this.buffer.addGeoCheckToBuffer(geoCheck);
this.logger.debug({
message: `Geo check job executed for monitor ${monitorId}`,
@@ -1,7 +1,8 @@
import type { Check } from "@/types/index.js";
import type { GeoCheck } from "@/types/index.js";
import type Logger from "@/utils/logger.js";
import type { IGeoChecksService } from "../business/geoChecksService.js";
import type { ILogger } from "@/utils/logger.js";
import type { ISettingsService } from "@/service/system/settingsService.js";
const SERVICE_NAME = "BufferService";
export interface IBufferService {
@@ -16,25 +17,15 @@ export interface IBufferService {
class BufferService implements IBufferService {
static SERVICE_NAME = SERVICE_NAME;
private BUFFER_TIMEOUT: number;
private logger: Logger;
private logger: ILogger;
private SERVICE_NAME: string;
private buffer: any[];
private geoBuffer: any[];
private bufferTimer: NodeJS.Timeout | null = null;
private checksService: any;
private geoChecksService: any;
private geoChecksService: IGeoChecksService;
constructor({
logger,
checkService,
geoChecksService,
settingsService,
}: {
logger: Logger;
checkService: any;
geoChecksService?: any;
settingsService: any;
}) {
constructor(logger: ILogger, checkService: any, geoChecksService: IGeoChecksService, settingsService: ISettingsService) {
this.BUFFER_TIMEOUT = settingsService.getSettings().nodeEnv === "development" ? 10 : 1000 * 60 * 1; // 1 minute
this.logger = logger;
this.checksService = checkService;
@@ -1,5 +1,5 @@
import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/types/geoCheck.js";
import type Logger from "@/utils/logger.js";
import type { ILogger } from "@/utils/logger.js";
import got from "got";
const SERVICE_NAME = "GlobalPingService";
@@ -57,9 +57,9 @@ export interface IGlobalPingService {
class GlobalPingService implements IGlobalPingService {
static SERVICE_NAME = SERVICE_NAME;
private logger: Logger;
private logger: ILogger;
constructor({ logger }: { logger: Logger }) {
constructor(logger: ILogger) {
this.logger = logger;
}
@@ -3,6 +3,8 @@ import type { Got, Response } from "got";
import type { Monitor, MonitorStatusResponse } from "@/types/index.js";
import CacheableLookup from "cacheable-lookup";
import { ISettingsService } from "../system/settingsService.js";
import { ILogger } from "@/utils/logger.js";
const SERVICE_NAME = "NetworkService";
type MonitorStatusResponseOverrides<T> = Partial<Omit<MonitorStatusResponse<T>, "monitorId" | "teamId" | "type">>;
@@ -111,31 +113,18 @@ class NetworkService implements INetworkService {
};
};
constructor({
axios,
got,
https,
jmespath,
GameDig,
ping,
logger,
http,
Docker,
net,
settingsService,
}: {
axios: any;
got: Got;
https: any;
jmespath: any;
GameDig: any;
ping: any;
logger: any;
http: any;
Docker: any;
net: any;
settingsService: any;
}) {
constructor(
axios: any,
got: Got,
https: any,
jmespath: any,
GameDig: any,
ping: any,
logger: ILogger,
Docker: any,
net: any,
settingsService: ISettingsService
) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";