Merge pull request #3801 from bluewave-labs/chore/cleanup

chore: cleanup
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-07-24 12:30:08 -07:00
commit 80ea09b864
78 files changed
+420 -557

No files matched your search

@@ -1,5 +1,5 @@
import { AppError } from "@/utils/AppError.js";
import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
import { UserRole } from "@/domain/users/user.type.js";
import sslChecker, { SSLDetails } from "ssl-checker";
type SSLCheckerType = typeof sslChecker;
+1 -1
View File
@@ -1,7 +1,7 @@
import { z } from "zod";
import { booleanCoercion } from "./shared.js";
import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js";
import { MonitorTypes } from "@/domain/monitors/monitor.types.js";
import { MonitorTypes } from "@/domain/monitors/monitor.type.js";
import { CheckFilters, DateRanges, SortOrders } from "@/types/query.js";
//****************************************
@@ -10,7 +10,7 @@ import {
MonitorStatuses,
MonitorTypes,
PageSpeedStrategies,
} from "@/domain/monitors/monitor.types.js";
} from "@/domain/monitors/monitor.type.js";
import { DateRanges, SortOrders } from "@/types/query.js";
const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" });
+1 -1
View File
@@ -126,7 +126,7 @@ export const buildShared = async ({
const checksRepository = new MongoChecksRepository(logger);
const incidentsRepository = new MongoIncidentsRepository();
const usersRepository = new MongoUsersRepository();
const geoChecksRepository = new MongoGeoChecksRepository(logger);
const geoChecksRepository = new MongoGeoChecksRepository();
const notificationsRepository = new MongoNotificationsRepository();
const jobsRepository = new MongoJobsRepository(workerId);
const queueWorkersRepository = new MongoQueueWorkersRepository();
@@ -1,9 +1,22 @@
import { CheckModel } from "@/domain/checks/check.model.js";
import mongoose from "mongoose";
import type { HardwareDiskStats, HardwareNetStats } from "@/domain/checks/check.type.js";
type DateRange = { start: Date; end: Date };
type HardwareUpChecks = { totalChecks: number };
// $avg yields null when no values are present, so metrics are nullable until the repository applies fallbacks
type NullableValues<T> = { [K in keyof T]: T[K] | null };
export type HardwareStatsBucket = {
_id: string;
avgCpuUsage: number | null;
avgMemoryUsage: number | null;
avgTemperature: number[];
disks: NullableValues<HardwareDiskStats>[];
net: NullableValues<HardwareNetStats>[];
};
const NET_RATE_FIELDS = [
["bytesSentPerSecond", "bytes_sent"],
["deltaBytesRecv", "bytes_recv"],
@@ -70,13 +83,13 @@ export const getHardwareUpChecks = async (monitorId: string, dates: DateRange):
return { totalChecks: count };
};
export const getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string) => {
export const getHardwareStats = async (monitorId: string, dates: DateRange, dateString: string): Promise<HardwareStatsBucket[]> => {
const netProjection = {
name: { $arrayElemAt: ["$sampleDoc.net.name", "$$nIdx"] },
...Object.fromEntries(NET_RATE_FIELDS.map(([outputKey, sourceField]) => [outputKey, netRatePerSecond(sourceField)])),
};
return await CheckModel.aggregate([
return await CheckModel.aggregate<HardwareStatsBucket>([
{
$match: {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
+1 -1
View File
@@ -1,5 +1,5 @@
import { Schema, model, Types } from "mongoose";
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
import type {
Check,
CheckAudits,
@@ -6,9 +6,9 @@ import type {
PageSpeedChecksResult,
UptimeChecksResult,
} from "@/domain/checks/check.type.js";
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
import { DateRange } from "@/types/query.js";
import { CheckFilter, DateRange } from "@/types/query.js";
export interface IChecksRepository {
// create
@@ -21,18 +21,18 @@ export interface IChecksRepository {
monitorId: string,
sortOrder: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
status: boolean | undefined
status: boolean | undefined,
filter?: CheckFilter
): Promise<ChecksQueryResult>;
findByTeamId(
sortOrder: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
teamId: string
teamId: string,
filter?: CheckFilter
): Promise<ChecksQueryResult>;
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
findByDateRangeAndMonitorId(
@@ -11,8 +11,9 @@ import type {
CheckMetadata,
CheckNetworkInterfaceInfo,
GotTimings,
HardwareCheckStats,
} from "@/domain/checks/check.type.js";
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
import mongoose from "mongoose";
import { getDateFormat, getDateForRange } from "@/utils/dataUtils.js";
@@ -20,7 +21,9 @@ import { ILogger } from "@/utils/logger.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
import { DateRange } from "@/types/query.js";
import { CheckFilter, DateRange } from "@/types/query.js";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR } from "@/types/network.js";
const SERVICE_NAME = "StatusService";
@@ -181,7 +184,12 @@ class MongoChecksRepository implements IChecksRepository {
// Map id to _id for MongoDB storage
const { id, metadata, ...rest } = check;
if (!metadata || !metadata.monitorId || !metadata.teamId) {
throw new Error(`Check must have valid metadata with monitorId and teamId. Got: ${JSON.stringify({ id, metadata })}`);
throw new AppError({
message: `Check must have valid metadata with monitorId and teamId. Got: ${JSON.stringify({ id, metadata })}`,
status: 500,
service: SERVICE_NAME,
method: "toDocument",
});
}
return {
_id: id ? new mongoose.Types.ObjectId(id) : new mongoose.Types.ObjectId(),
@@ -205,46 +213,41 @@ class MongoChecksRepository implements IChecksRepository {
return this.mapDocuments(inserted as unknown as CheckDocument[]);
};
private filterToMatch = (filter: CheckFilter | undefined): Record<string, unknown> => {
switch (filter) {
case "up":
return { status: true };
case "down":
return { status: false };
case "resolve":
return { status: false, statusCode: NETWORK_ERROR };
default:
this.logger.warn({
message: "invalid filter",
service: SERVICE_NAME,
method: "filterToMatch",
});
return {};
}
};
findByMonitorId = async (
monitorId: string,
sortOrder: string,
dateRange: DateRange,
filter: string | undefined,
page: number,
rowsPerPage: number,
status: boolean | undefined
status: boolean | undefined,
filter?: CheckFilter
) => {
// Match
const matchStage: Record<string, unknown> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
...(typeof status !== "undefined" && { status }),
createdAt: { $gte: getDateForRange(dateRange) },
...this.filterToMatch(filter),
};
if (filter !== undefined) {
switch (filter) {
case "all":
break;
case "up":
matchStage.status = true;
break;
case "down":
matchStage.status = false;
break;
case "resolve":
matchStage.status = false;
matchStage.statusCode = 5000;
break;
default:
this.logger.warn({
message: "invalid filter",
service: SERVICE_NAME,
method: "getChecks",
});
break;
}
}
//Sort
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
@@ -262,35 +265,12 @@ class MongoChecksRepository implements IChecksRepository {
return { checksCount, checks: this.mapDocuments(checks) };
};
findByTeamId = async (sortOrder: string, dateRange: DateRange, filter: string, page: number, rowsPerPage: number, teamId: string) => {
findByTeamId = async (sortOrder: string, dateRange: DateRange, page: number, rowsPerPage: number, teamId: string, filter?: CheckFilter) => {
const matchStage: Record<string, unknown> = {
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
createdAt: { $gte: getDateForRange(dateRange) },
...this.filterToMatch(filter),
};
// Add filter to match stage
if (filter !== undefined) {
switch (filter) {
case "all":
break;
case "up":
matchStage.status = true;
break;
case "down":
matchStage.status = false;
break;
case "resolve":
matchStage.status = false;
matchStage.statusCode = 5000;
break;
default:
this.logger.warn({
message: "invalid filter",
service: SERVICE_NAME,
method: "getChecksByTeam",
});
break;
}
}
const parsedSortOrder = sortOrder === "asc" ? 1 : -1;
@@ -533,33 +513,35 @@ class MongoChecksRepository implements IChecksRepository {
totalChecks: upChecksDoc?.totalChecks ?? 0,
};
const checks = (hardwareMetrics ?? []).map((metric) => ({
bucketDate: metric._id,
avgCpuUsage: metric.avgCpuUsage ?? 0,
avgMemoryUsage: metric.avgMemoryUsage ?? 0,
avgTemperature: metric.avgTemperature ?? [],
disks: (metric.disks ?? []).map((disk: { [key: string]: number | string | undefined }) => ({
name: disk?.name ?? "",
readSpeed: disk?.readSpeed ?? 0,
writeSpeed: disk?.writeSpeed ?? 0,
totalBytes: disk?.totalBytes ?? 0,
freeBytes: disk?.freeBytes ?? 0,
usagePercent: disk?.usagePercent ?? 0,
})),
net: (metric.net ?? []).map((iface: { [key: string]: number | string | undefined }) => ({
name: iface?.name ?? "",
bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0,
deltaBytesRecv: iface?.deltaBytesRecv ?? 0,
deltaPacketsSent: iface?.deltaPacketsSent ?? 0,
deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0,
deltaErrIn: iface?.deltaErrIn ?? 0,
deltaErrOut: iface?.deltaErrOut ?? 0,
deltaDropIn: iface?.deltaDropIn ?? 0,
deltaDropOut: iface?.deltaDropOut ?? 0,
deltaFifoIn: iface?.deltaFifoIn ?? 0,
deltaFifoOut: iface?.deltaFifoOut ?? 0,
})),
}));
const checks = (hardwareMetrics ?? []).map(
(metric): HardwareCheckStats => ({
bucketDate: metric._id,
avgCpuUsage: metric.avgCpuUsage ?? 0,
avgMemoryUsage: metric.avgMemoryUsage ?? 0,
avgTemperature: metric.avgTemperature ?? [],
disks: (metric.disks ?? []).map((disk) => ({
name: disk?.name ?? "",
readSpeed: disk?.readSpeed ?? 0,
writeSpeed: disk?.writeSpeed ?? 0,
totalBytes: disk?.totalBytes ?? 0,
freeBytes: disk?.freeBytes ?? 0,
usagePercent: disk?.usagePercent ?? 0,
})),
net: (metric.net ?? []).map((iface) => ({
name: iface?.name ?? "",
bytesSentPerSecond: iface?.bytesSentPerSecond ?? 0,
deltaBytesRecv: iface?.deltaBytesRecv ?? 0,
deltaPacketsSent: iface?.deltaPacketsSent ?? 0,
deltaPacketsRecv: iface?.deltaPacketsRecv ?? 0,
deltaErrIn: iface?.deltaErrIn ?? 0,
deltaErrOut: iface?.deltaErrOut ?? 0,
deltaDropIn: iface?.deltaDropIn ?? 0,
deltaDropOut: iface?.deltaDropOut ?? 0,
deltaFifoIn: iface?.deltaFifoIn ?? 0,
deltaFifoOut: iface?.deltaFifoOut ?? 0,
})),
})
);
return {
monitorType: "hardware" as const,
+8 -8
View File
@@ -6,7 +6,7 @@ import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.j
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import { ILogger } from "@/utils/logger.js";
import { DateRange } from "@/types/query.js";
import { CheckFilter, DateRange } from "@/types/query.js";
const SERVICE_NAME = "checkService";
@@ -22,7 +22,7 @@ export interface ICheckService {
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
filter?: CheckFilter;
status?: boolean;
}): Promise<ChecksQueryResult>;
getChecksByTeam(params: {
@@ -31,7 +31,7 @@ export interface ICheckService {
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
filter?: CheckFilter;
}): Promise<ChecksQueryResult>;
getChecksSummaryByTeamId(params: { teamId: string; dateRange: DateRange }): Promise<ChecksSummary>;
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
@@ -169,7 +169,7 @@ export class CheckService implements ICheckService {
page: number;
rowsPerPage: number;
status?: boolean;
filter?: string;
filter: CheckFilter;
}) => {
if (!monitorId) {
throw new AppError({ message: "No monitor ID in request", service: SERVICE_NAME, method: "getChecksByMonitor", status: 400 });
@@ -184,7 +184,7 @@ export class CheckService implements ICheckService {
const parsedPage = page ?? 0;
const parsedRowsPerPage = rowsPerPage ?? 5;
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, status);
const result = await this.checksRepository.findByMonitorId(monitorId, sortOrder, dateRange, parsedPage, parsedRowsPerPage, status, filter);
return result;
};
@@ -193,21 +193,21 @@ export class CheckService implements ICheckService {
teamId,
sortOrder,
dateRange,
filter,
page,
rowsPerPage,
filter,
}: {
teamId: string;
sortOrder: string;
dateRange: DateRange;
page: number;
rowsPerPage: number;
filter?: string;
filter?: CheckFilter;
}) => {
const parsedPage = page ?? 0;
const parsedRowsPerPage = rowsPerPage ?? 5;
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, filter, parsedPage, parsedRowsPerPage, teamId);
const checkData = await this.checksRepository.findByTeamId(sortOrder, dateRange, parsedPage, parsedRowsPerPage, teamId, filter);
return checkData;
};
+47 -38
View File
@@ -1,4 +1,4 @@
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
import type { Response } from "got";
export const CHECK_TTL_SENTINEL = 366;
@@ -126,43 +126,6 @@ export interface PageSpeedChecksResult {
groupedChecks: PageSpeedGroupedCheck[];
}
export interface HardwareChecksResult {
monitorType: "hardware";
aggregateData: {
totalChecks: number;
};
upChecks: {
totalChecks: number;
};
checks: Array<{
bucketDate: string;
avgCpuUsage: number;
avgMemoryUsage: number;
avgTemperature: number[];
disks: Array<{
name: string;
readSpeed: number;
writeSpeed: number;
totalBytes: number;
freeBytes: number;
usagePercent: number;
}>;
net: Array<{
name: string;
bytesSentPerSecond: number;
deltaBytesRecv: number;
deltaPacketsSent: number;
deltaPacketsRecv: number;
deltaErrIn: number;
deltaErrOut: number;
deltaDropIn: number;
deltaDropOut: number;
deltaFifoIn: number;
deltaFifoOut: number;
}>;
}>;
}
export interface GroupedCheck {
bucketDate: string;
avgResponseTime: number;
@@ -205,3 +168,49 @@ export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
};
export type CheckSnapshot = Omit<Check, "metadata" | "updatedAt">;
export interface HardwareDiskStats {
name: string;
readSpeed: number;
writeSpeed: number;
totalBytes: number;
freeBytes: number;
usagePercent: number;
}
export interface HardwareNetStats {
name: string;
bytesSentPerSecond: number;
deltaBytesRecv: number;
deltaPacketsSent: number;
deltaPacketsRecv: number;
deltaErrIn: number;
deltaErrOut: number;
deltaDropIn: number;
deltaDropOut: number;
deltaFifoIn: number;
deltaFifoOut: number;
}
export interface HardwareCheckStats {
bucketDate: string;
avgCpuUsage: number;
avgMemoryUsage: number;
avgTemperature: number[];
disks: HardwareDiskStats[];
net: HardwareNetStats[];
}
export interface HardwareStats {
aggregateData: {
totalChecks: number;
};
upChecks: {
totalChecks: number;
};
checks: HardwareCheckStats[];
}
export interface HardwareChecksResult extends HardwareStats {
monitorType: "hardware";
}
@@ -1,5 +1,5 @@
import { Schema, model, Types } from "mongoose";
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.type.js";
import type { GeoCheck, GeoCheckLocation, GeoCheckMetadata, GeoCheckResult, GeoCheckTimings } from "@/domain/geo-checks/geo-check.type.js";
type GeoCheckMetadataDocument = Omit<GeoCheckMetadata, "monitorId" | "teamId"> & {
@@ -22,7 +22,6 @@ export interface IGeoChecksRepository {
rowsPerPage: number,
continents?: GeoContinent[]
): Promise<FlatGeoChecksQueryResult>;
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
findGroupedByMonitorIdAndDateRange(monitorId: string, DateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]>;
deleteByMonitorId(monitorId: string): Promise<number>;
deleteByTeamId(teamId: string): Promise<number>;
@@ -4,7 +4,6 @@ import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js";
import mongoose, { PipelineStage } from "mongoose";
import { getDateForRange, getDateFormat } from "@/utils/dataUtils.js";
import { ILogger } from "@/utils/logger.js";
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
import { DateRange } from "@/types/query.js";
@@ -13,11 +12,6 @@ const SERVICE_NAME = "GeoChecksRepository";
class MongoGeoChecksRepository implements IGeoChecksRepository {
static SERVICE_NAME = SERVICE_NAME;
private logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
}
private toEntity = (doc: GeoCheckDocument): GeoCheck => {
const mapMetadata = (metadata: GeoCheckMetadataDocument): GeoCheckMetadata => ({
monitorId: toStringId(metadata.monitorId),
@@ -64,28 +58,18 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
};
createGeoChecks = async (geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]> => {
try {
const docs = await GeoCheckModel.insertMany(
geoChecks.map((geoCheck) => ({
metadata: {
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
type: geoCheck.metadata.type,
},
results: geoCheck.results,
expiry: new Date(geoCheck.expiry),
}))
);
return docs.map((doc) => this.toEntity(doc));
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Failed to createGeoChecks: ${error.message}` : "Failed to createGeoChecks",
service: SERVICE_NAME,
method: "createGeoChecks",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
const docs = await GeoCheckModel.insertMany(
geoChecks.map((geoCheck) => ({
metadata: {
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
type: geoCheck.metadata.type,
},
results: geoCheck.results,
expiry: new Date(geoCheck.expiry),
}))
);
return docs.map((doc) => this.toEntity(doc));
};
findByMonitorId = async (
@@ -96,119 +80,85 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
rowsPerPage: number,
continents?: GeoContinent[]
): Promise<FlatGeoChecksQueryResult> => {
try {
const matchStage: Record<string, unknown> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: { $gte: getDateForRange(dateRange) },
};
const matchStage: Record<string, unknown> = {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: { $gte: getDateForRange(dateRange) },
};
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
// Common pipeline stages for both paths
const pipeline: PipelineStage[] = [
{ $match: matchStage },
{ $unwind: "$results" },
// Filter by continent if specified
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
// Project to flat structure
{
$project: {
_id: 0,
monitorId: "$metadata.monitorId",
teamId: "$metadata.teamId",
type: "$metadata.type",
location: "$results.location",
status: "$results.status",
statusCode: "$results.statusCode",
timings: "$results.timings",
createdAt: 1,
updatedAt: 1,
},
},
{ $sort: { createdAt: convertedSortOrder } },
{ $skip: skip },
{ $limit: rowsPerPage },
];
// Count pipeline
const countPipeline: PipelineStage[] = [
{ $match: matchStage },
{ $unwind: "$results" },
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
{ $count: "count" },
];
const [countResult, dataResults] = await Promise.all([GeoCheckModel.aggregate(countPipeline), GeoCheckModel.aggregate(pipeline)]);
const geoChecksCount = countResult[0]?.count || 0;
const geoChecks: FlatGeoCheck[] = dataResults.map((doc) => ({
id: `${doc.monitorId.toString()}-${new Date(doc.createdAt).getTime()}-${doc.location.continent}-${doc.location.city}-${Math.random().toString(36).substring(2, 15)}`,
monitorId: doc.monitorId.toString(),
teamId: doc.teamId.toString(),
type: doc.type,
location: doc.location,
status: doc.status,
statusCode: doc.statusCode,
timings: doc.timings,
createdAt: new Date(doc.createdAt).toISOString(),
updatedAt: new Date(doc.updatedAt).toISOString(),
}));
return { geoChecksCount, geoChecks };
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Error finding geo checks by monitor ID: ${error.message}` : "Error finding geo checks by monitor ID",
service: SERVICE_NAME,
method: "findByMonitorId",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
};
findByMonitorIdAndDateRange = async (monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]> => {
try {
const docs = await GeoCheckModel.find({
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: {
$gte: startDate,
$lte: endDate,
// Common pipeline stages for both paths
const pipeline: PipelineStage[] = [
{ $match: matchStage },
{ $unwind: "$results" },
// Filter by continent if specified
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
// Project to flat structure
{
$project: {
_id: 0,
monitorId: "$metadata.monitorId",
teamId: "$metadata.teamId",
type: "$metadata.type",
location: "$results.location",
status: "$results.status",
statusCode: "$results.statusCode",
timings: "$results.timings",
createdAt: 1,
updatedAt: 1,
},
}).sort({ createdAt: -1 });
return docs.map(this.toEntity);
} catch (error: unknown) {
this.logger.error({
message:
error instanceof Error
? `Error finding geo checks by monitor ID and date range: ${error.message}`
: "Error finding geo checks by monitor ID and date range",
service: SERVICE_NAME,
method: "findByMonitorIdAndDateRange",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
},
{ $sort: { createdAt: convertedSortOrder } },
{ $skip: skip },
{ $limit: rowsPerPage },
];
// Count pipeline
const countPipeline: PipelineStage[] = [
{ $match: matchStage },
{ $unwind: "$results" },
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
{ $count: "count" },
];
const [countResult, dataResults] = await Promise.all([GeoCheckModel.aggregate(countPipeline), GeoCheckModel.aggregate(pipeline)]);
const geoChecksCount = countResult[0]?.count || 0;
const geoChecks: FlatGeoCheck[] = dataResults.map((doc) => ({
id: `${doc.monitorId.toString()}-${new Date(doc.createdAt).getTime()}-${doc.location.continent}-${doc.location.city}-${Math.random().toString(36).substring(2, 15)}`,
monitorId: doc.monitorId.toString(),
teamId: doc.teamId.toString(),
type: doc.type,
location: doc.location,
status: doc.status,
statusCode: doc.statusCode,
timings: doc.timings,
createdAt: new Date(doc.createdAt).toISOString(),
updatedAt: new Date(doc.updatedAt).toISOString(),
}));
return { geoChecksCount, geoChecks };
};
findGroupedByMonitorIdAndDateRange = async (monitorId: string, dateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]> => {
@@ -216,146 +166,106 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
const dateString = getDateFormat(dateRange);
const end = new Date();
try {
const pipeline: PipelineStage[] = [
// Match geo checks for this monitor in date range
{
$match: {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: {
$gte: start,
$lte: end,
const pipeline: PipelineStage[] = [
// Match geo checks for this monitor in date range
{
$match: {
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
createdAt: {
$gte: start,
$lte: end,
},
},
},
// Unwind the results array to process each location separately
{
$unwind: "$results",
},
// Filter by continent if specified
...(continents && continents.length > 0
? [
{
$match: {
"results.location.continent": { $in: continents },
},
},
]
: []),
// Group by date bucket and continent
{
$group: {
_id: {
bucketDate: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
continent: "$results.location.continent",
},
avgResponseTime: { $avg: "$results.timings.total" },
totalChecks: { $sum: 1 },
upChecks: {
$sum: {
$cond: ["$results.status", 1, 0],
},
},
},
// Unwind the results array to process each location separately
{
$unwind: "$results",
},
// Filter by continent if specified
...(continents && continents.length > 0
? [
},
// Calculate uptime percentage
{
$project: {
_id: 0,
bucketDate: "$_id.bucketDate",
continent: "$_id.continent",
avgResponseTime: { $round: ["$avgResponseTime", 2] },
totalChecks: 1,
uptimePercentage: {
$round: [
{
$match: {
"results.location.continent": { $in: continents },
},
$multiply: [
{
$divide: ["$upChecks", "$totalChecks"],
},
100,
],
},
]
: []),
// Group by date bucket and continent
{
$group: {
_id: {
bucketDate: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
continent: "$results.location.continent",
},
avgResponseTime: { $avg: "$results.timings.total" },
totalChecks: { $sum: 1 },
upChecks: {
$sum: {
$cond: ["$results.status", 1, 0],
},
},
2,
],
},
},
// Calculate uptime percentage
{
$project: {
_id: 0,
bucketDate: "$_id.bucketDate",
continent: "$_id.continent",
avgResponseTime: { $round: ["$avgResponseTime", 2] },
totalChecks: 1,
uptimePercentage: {
$round: [
{
$multiply: [
{
$divide: ["$upChecks", "$totalChecks"],
},
100,
],
},
2,
],
},
},
},
// Sort by date and continent
{
$sort: {
bucketDate: 1,
continent: 1,
},
// Sort by date and continent
{
$sort: {
bucketDate: 1,
continent: 1,
},
},
];
},
];
const results = await GeoCheckModel.aggregate(pipeline);
return results as GroupedGeoCheck[];
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Error finding grouped geo checks: ${error.message}` : "Error finding grouped geo checks",
service: SERVICE_NAME,
method: "findGroupedByMonitorIdAndDateRange",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
const results = await GeoCheckModel.aggregate(pipeline);
return results as GroupedGeoCheck[];
};
deleteByMonitorId = async (monitorId: string): Promise<number> => {
try {
const result = await GeoCheckModel.deleteMany({
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
});
return result.deletedCount || 0;
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Error deleting geo checks by monitor ID: ${error.message}` : "Error deleting geo checks by monitor ID",
service: SERVICE_NAME,
method: "deleteByMonitorId",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
const result = await GeoCheckModel.deleteMany({
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
});
return result.deletedCount || 0;
};
deleteByTeamId = async (teamId: string): Promise<number> => {
try {
const result = await GeoCheckModel.deleteMany({
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
});
return result.deletedCount || 0;
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Error deleting geo checks by team ID: ${error.message}` : "Error deleting geo checks by team ID",
service: SERVICE_NAME,
method: "deleteByTeamId",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
const result = await GeoCheckModel.deleteMany({
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
});
return result.deletedCount || 0;
};
deleteByMonitorIdsNotIn = async (monitorIds: string[]): Promise<number> => {
try {
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
return result.deletedCount || 0;
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? `Error deleting orphaned geo checks: ${error.message}` : "Error deleting orphaned geo checks",
service: SERVICE_NAME,
method: "deleteByMonitorIdsNotIn",
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
const result = await GeoCheckModel.deleteMany({ "metadata.monitorId": { $nin: objectIds } });
return result.deletedCount || 0;
};
}
@@ -1,5 +1,5 @@
import type { GeoCheck } from "@/domain/geo-checks/geo-check.type.js";
import type { Monitor } from "@/domain/monitors/monitor.types.js";
import type { Monitor } from "@/domain/monitors/monitor.type.js";
import type { GeoCheckResult, GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
import { Types } from "mongoose";
import type { FlatGeoChecksQueryResult, IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js";
@@ -1,4 +1,4 @@
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
import type { MonitorType } from "@/domain/monitors/monitor.type.js";
export const GeoContinents = ["EU", "NA", "AS", "SA", "AF", "OC"] as const;
export type GeoContinent = (typeof GeoContinents)[number];
@@ -1,5 +1,5 @@
const SERVICE_NAME = "incidentService";
import type { Monitor } from "@/domain/monitors/monitor.types.js";
import type { Monitor } from "@/domain/monitors/monitor.type.js";
import type { MonitorStatusResponse } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import { getDateForRange } from "@/utils/dataUtils.js";
+2 -2
View File
@@ -1,6 +1,6 @@
import { Schema, model, Types } from "mongoose";
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/domain/monitors/monitor.types.js";
import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies, HttpMethods } from "@/domain/monitors/monitor.types.js";
import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/domain/monitors/monitor.type.js";
import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies, HttpMethods } from "@/domain/monitors/monitor.type.js";
import type {
CheckAudits,
CheckCaptureInfo,
@@ -1,4 +1,4 @@
import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.types.js";
import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.type.js";
export interface TeamQueryConfig {
limit?: number;
@@ -26,9 +26,9 @@ export interface IMonitorsRepository {
findByIdLean(monitorId: string): Promise<Monitor | null>;
// collection fetch
findAll(): Promise<Monitor[] | null>;
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null>;
findAll(): Promise<Monitor[]>;
findByTeamId(teamId: string, config: TeamQueryConfig): Promise<Monitor[]>;
findByTeamIdWithStats(teamId: string, config: TeamQueryConfig): Promise<Monitor[]>;
findByIds(monitorIds: string[]): Promise<Monitor[]>;
findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise<Monitor[]>;
@@ -1,7 +1,7 @@
import { MonitorModel } from "@/domain/monitors/monitor.model.js";
import type { MonitorDocument, CheckSnapshotDocument } from "@/domain/monitors/monitor.model.js";
import type { CheckSnapshot } from "@/domain/checks/check.type.js";
import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.types.js";
import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.type.js";
import mongoose, { type FilterQuery, type PipelineStage } from "mongoose";
import { MongoBulkWriteError } from "mongodb";
import { AppError } from "@/utils/AppError.js";
@@ -96,14 +96,14 @@ class MongoMonitorsRepository implements IMonitorsRepository {
return { sort: { [field]: order === "asc" ? 1 : -1 } as const, skip: Math.max(page, 0) * rowsPerPage, limit: rowsPerPage };
};
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[]> => {
const query = this.queryBuilder(config, teamId);
const { sort, skip, limit } = this.pageOptions(config);
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limit);
return this.mapDocuments(documents);
};
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[]> => {
const { sort, skip, limit } = this.pageOptions(config);
const query = this.queryBuilder(config, teamId);
@@ -1,5 +1,5 @@
import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js";
import { type Monitor } from "@/domain/monitors/monitor.types.js";
import { type Monitor } from "@/domain/monitors/monitor.type.js";
import type {
MonitorType,
MonitorsWithChecksByTeamIdResult,
@@ -8,8 +8,8 @@ import type {
PageSpeedDetailsResult,
GamesMap,
GroupedGeoCheckResult,
} from "@/domain/monitors/monitor.types.js";
import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.types.js";
} from "@/domain/monitors/monitor.type.js";
import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.type.js";
import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/domain/checks/check.type.js";
import type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
import type { IChecksRepository } from "@/domain/checks/check.repository.interface.js";
@@ -318,7 +318,7 @@ export class MonitorService implements IMonitorService {
type?: MonitorType | MonitorType[];
tags?: string | string[];
filter?: string;
}): Promise<Monitor[] | null> => {
}): Promise<Monitor[]> => {
return await this.monitorsRepository.findByTeamId(teamId, { type, tags, filter });
};
@@ -356,13 +356,12 @@ export class MonitorService implements IMonitorService {
order,
});
const monitorsList = (monitors ?? []) as Monitor[];
const snapshotTypes: MonitorType[] = ["hardware"];
const requestedTypes = Array.isArray(type) ? type : type ? [type] : [];
const snapshotOnlyRequest =
requestedTypes.length > 0 && requestedTypes.every((requestedType) => snapshotTypes.includes(requestedType as MonitorType));
const monitorsWithChecks = monitorsList.map((monitor: Monitor) => {
const monitorsWithChecks = monitors.map((monitor: Monitor) => {
const rawChecks = monitor.recentChecks ?? [];
const isSnapshotType = snapshotOnlyRequest || snapshotTypes.includes(monitor.type);
const checks = isSnapshotType ? rawChecks.slice(0, 1) : NormalizeData(rawChecks, 10, 100);
@@ -523,7 +522,7 @@ export class MonitorService implements IMonitorService {
exportMonitorsToJSON = async ({ teamId }: { teamId: string }): Promise<Monitor[]> => {
const monitors = await this.monitorsRepository.findByTeamId(teamId, {});
if (!monitors || monitors.length === 0) {
if (monitors.length === 0) {
throw new AppError({ message: "No monitors found to export.", service: SERVICE_NAME, method: "exportMonitorsToJSON", status: 400 });
}
@@ -3,6 +3,7 @@ export type { CheckSnapshot } from "@/domain/checks/check.type.js";
import type { GeoContinent, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
export type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js";
import http from "node:http";
import { HardwareStats } from "@/domain/checks/check.type.js";
export const HttpStatusCodes = [
...Object.keys(http.STATUS_CODES).map(Number),
@@ -149,48 +150,6 @@ export interface UptimeDetailsResult {
monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null;
}
export interface HardwareDiskStats {
name: string;
readSpeed: number;
writeSpeed: number;
totalBytes: number;
freeBytes: number;
usagePercent: number;
}
export interface HardwareNetStats {
name: string;
bytesSentPerSecond: number;
deltaBytesRecv: number;
deltaPacketsSent: number;
deltaPacketsRecv: number;
deltaErrIn: number;
deltaErrOut: number;
deltaDropIn: number;
deltaDropOut: number;
deltaFifoIn: number;
deltaFifoOut: number;
}
export interface HardwareCheckStats {
bucketDate: string;
avgCpuUsage: number;
avgMemoryUsage: number;
avgTemperature: number[];
disks: HardwareDiskStats[];
net: HardwareNetStats[];
}
export interface HardwareStats {
aggregateData: {
totalChecks: number;
};
upChecks: {
totalChecks: number;
};
checks: HardwareCheckStats[];
}
export interface HardwareDetailsResult {
monitor: Monitor;
stats: HardwareStats;
@@ -1,4 +1,4 @@
import type { Monitor } from "@/domain/monitors/monitor.types.js";
import type { Monitor } from "@/domain/monitors/monitor.type.js";
import type { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
import type {
@@ -1,4 +1,4 @@
import type { Monitor } from "@/domain/monitors/monitor.types.js";
import type { Monitor } from "@/domain/monitors/monitor.type.js";
import type { Notification } from "@/domain/notifications/notification.type.js";
import type { MonitorStatusResponse } from "@/types/network.js";
import type { NotificationMessage } from "@/domain/notifications/notification.type.js";
@@ -9,7 +9,7 @@ import {
} from "@/domain/status-pages/status-page.type.js";
import { AppError } from "@/utils/AppError.js";
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import { NormalizeData } from "@/utils/dataUtils.js";
export interface IStatusPageService {
@@ -1,4 +1,4 @@
import type { Monitor } from "@/domain/monitors/monitor.types.js";
import type { Monitor } from "@/domain/monitors/monitor.type.js";
import type { NormalizedCheck, CheckSnapshot } from "@/domain/checks/check.type.js";
export const StatusPageTypes = ["uptime", "infrastructure"] as const;
export type StatusPageType = (typeof StatusPageTypes)[number];
@@ -71,7 +71,7 @@ class MongoUsersRepository implements IUsersRepository {
findById = async (id: string) => {
const user = await UserModel.findById(id).select("-password").select("-profileImage");
if (!user) {
throw new Error("User not found");
throw new AppError({ message: "User not found", service: SERVICE_NAME, status: 404 });
}
return this.toEntity(user);
+2 -2
View File
@@ -1,6 +1,6 @@
import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/domain/geo-checks/geo-check.type.js";
import { supportsGeoCheck, type HttpStatusCode } from "@/domain/monitors/monitor.types.js";
import { MonitorType } from "@/domain/monitors/monitor.types.js";
import { supportsGeoCheck, type HttpStatusCode } from "@/domain/monitors/monitor.type.js";
import { MonitorType } from "@/domain/monitors/monitor.type.js";
import type { ILogger } from "@/utils/logger.js";
import got from "got";
import { isStatusUp } from "@/service/network/utils.js";
@@ -1,4 +1,4 @@
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import jmespath from "jmespath";
import RE2 from "re2";
type JmesPath = typeof jmespath;
+3 -2
View File
@@ -1,9 +1,10 @@
import { DNSStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import type { Resolver } from "dns/promises";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
import { timeRequest } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
const SERVICE_NAME = "DNSProvider";
+3 -3
View File
@@ -1,11 +1,11 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { ILogger } from "@/utils/logger.js";
import { AppError } from "@/utils/AppError.js";
import Dockerode from "dockerode";
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
import { timeRequest } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
type DockerodeType = typeof Dockerode;
const SERVICE_NAME = "DockerProvider";
+2 -2
View File
@@ -1,9 +1,9 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { GameStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { GameDig } from "gamedig";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
import { ILogger } from "@/utils/logger.js";
type GameDigType = typeof GameDig;
+1 -1
View File
@@ -1,6 +1,6 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { GrpcStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { AppError } from "@/utils/AppError.js";
import { timeRequest } from "@/service/network/utils.js";
import * as grpc from "@grpc/grpc-js";
@@ -1,6 +1,6 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { HttpProvider } from "@/service/network/HttpProvider.js";
import { AppError } from "@/utils/AppError.js";
+3 -2
View File
@@ -5,8 +5,9 @@ import { HttpStatusPayload } from "@/types/network.js";
import { MonitorStatusResponse } from "@/types/network.js";
import { Agent as HttpsAgent } from "https";
import { Agent as HttpAgent } from "http";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { NETWORK_ERROR, isStatusUp } from "@/service/network/utils.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { isStatusUp } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
import CacheableLookup from "cacheable-lookup";
export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
@@ -1,4 +1,4 @@
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { MonitorStatusResponse } from "@/types/network.js";
export interface IStatusProvider<T> {
@@ -1,6 +1,6 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js";
import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/domain/monitors/monitor.type.js";
import { HttpProvider } from "@/service/network/HttpProvider.js";
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
import { ILogger } from "@/utils/logger.js";
+1 -1
View File
@@ -1,6 +1,6 @@
import { PingStatusPayload } from "@/types/network.js";
import { IStatusProvider } from "./IStatusProvider.js";
import { MonitorType, Monitor } from "@/domain/monitors/monitor.types.js";
import { MonitorType, Monitor } from "@/domain/monitors/monitor.type.js";
import { MonitorStatusResponse } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
import ping from "ping";
+3 -2
View File
@@ -1,8 +1,9 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { PortStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
import { timeRequest } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
import * as net from "net";
type NetType = typeof net;
@@ -1,8 +1,9 @@
import { IStatusProvider } from "@/service/network/IStatusProvider.js";
import { WebSocketStatusPayload, MonitorStatusResponse } from "@/types/network.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js";
import { timeRequest } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
import type WebSocket from "ws";
type WebSocketConstructor = typeof WebSocket;
+1 -4
View File
@@ -1,4 +1,4 @@
import type { HttpStatusCode } from "@/domain/monitors/monitor.types.js";
import type { HttpStatusCode } from "@/domain/monitors/monitor.type.js";
export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> => {
const start = process.hrtime.bigint();
@@ -12,9 +12,6 @@ export const timeRequest = async <T>(operation: () => Promise<T>): Promise<{ res
}
};
export const NETWORK_ERROR = 5000;
export const PING_ERROR = 5001;
export const isStatusUp = (statusCode: number | undefined, customUpCodes: HttpStatusCode[] = []): boolean => {
if (statusCode === undefined) return false;
return (statusCode >= 200 && statusCode < 300) || customUpCodes.includes(statusCode);
+2 -2
View File
@@ -1,8 +1,8 @@
import type { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js";
import type { Monitor, MonitorType } from "@/domain/monitors/monitor.type.js";
import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.js";
import type { AxiosStatic } from "axios";
import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR } from "@/service/network/utils.js";
import { NETWORK_ERROR } from "@/types/network.js";
import { ILogger } from "@/utils/logger.js";
import { IStatusProvider } from "./network/IStatusProvider.js";
const SERVICE_NAME = "NetworkService";
+2 -2
View File
@@ -1,7 +1,7 @@
import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js";
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
import type { Check, CheckDiskInfo, CheckSnapshot } from "@/domain/checks/check.type.js";
import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.types.js";
import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.type.js";
import type {
DockerStatusPayload,
GameStatusPayload,
@@ -17,7 +17,7 @@ import type {
import { AppError } from "@/utils/AppError.js";
import { ILogger } from "@/utils/logger.js";
import type { HardwareStatusMetrics } from "@/types/network.js";
import { MAX_RECENT_CHECKS } from "@/domain/monitors/monitor.types.js";
import { MAX_RECENT_CHECKS } from "@/domain/monitors/monitor.type.js";
const SERVICE_NAME = "StatusService";
const HARDWARE_ALERT_COUNTER_START = 5;
+3 -1
View File
@@ -9,10 +9,12 @@ import type {
GotTimings,
ILighthouseAudit,
} from "@/domain/checks/check.type.js";
import type { DnsRecordType, Monitor, MonitorMatchMethod, MonitorStatus, MonitorType } from "@/domain/monitors/monitor.types.js";
import type { DnsRecordType, Monitor, MonitorMatchMethod, MonitorStatus, MonitorType } from "@/domain/monitors/monitor.type.js";
import type { QueryResult } from "gamedig";
export const NETWORK_ERROR = 5000;
export interface MonitorStatusResponse<
T =
| HttpStatusPayload
+1 -1
View File
@@ -1,7 +1,7 @@
import { MonitorStatusResponse } from "@/types/network.js";
import { MonitorEvaluation } from "@/worker/worker.interface.js";
import { Check } from "@/domain/checks/check.type.js";
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import { IMonitorStatusPolicy } from "@/worker/worker.monitor-status-policy.js";
import { IStatusService } from "@/service/statusService.js";
+2 -2
View File
@@ -1,11 +1,11 @@
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import { MonitorEvaluation } from "@/worker/worker.interface.js";
import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js";
import { isWindowActive } from "@/utils/maintenanceWindow.js";
import { ILogger } from "@/utils/logger.js";
import { IBufferService } from "@/service/bufferService.js";
import { AppError } from "@/utils/AppError.js";
import { supportsGeoCheck } from "@/domain/monitors/monitor.types.js";
import { supportsGeoCheck } from "@/domain/monitors/monitor.type.js";
import { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
const SERVICE_NAME = "CheckPipeline";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import { Check } from "@/domain/checks/check.type.js";
import { MonitorStatusResponse } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Monitor } from "@/domain/monitors/monitor.types.js";
import { Monitor } from "@/domain/monitors/monitor.type.js";
import { Check } from "@/domain/checks/check.type.js";
import { JobType } from "@/domain/jobs/job.type.js";
import { MonitorPayloadMap, MonitorStatusResponse, StatusChangeResult } from "@/types/network.js";
+2 -2
View File
@@ -1,7 +1,7 @@
import { IJobScheduler } from "@/worker/worker.interface.js";
import { IJobsRepository } from "@/domain/jobs/job.repository.interface.js";
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.types.js";
import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.type.js";
import { type Job, type JobSeed, type JobType, jobId } from "@/domain/jobs/job.type.js";
import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js";
import { WorkerJobsPagination, WorkerJobSummary, WorkerMetrics } from "@/worker/worker.interface.js";
@@ -187,7 +187,7 @@ export class JobScheduler implements IJobScheduler {
// Seed queue
protected reconcile = async () => {
const now = Date.now();
const monitors = (await this.monitorsRepository.findAll()) ?? [];
const monitors = await this.monitorsRepository.findAll();
for (const monitor of monitors) {
await this.jobsRepository.upsertJob(this.toCheckJob(monitor, now));
if (supportsGeoCheck(monitor.type) && monitor.geoCheckEnabled) {
@@ -1,6 +1,6 @@
import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/domain/monitors/monitor.repository.interface.ts";
import type { CheckSnapshot } from "../../src/domain/checks/check.type.ts";
import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.types.ts";
import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.type.ts";
export class InMemoryMonitorsRepository implements IMonitorsRepository {
private monitors: Monitor[] = [];
+1 -1
View File
@@ -10,7 +10,7 @@ import { IncidentService } from "../../src/domain/incidents/incident.service.ts"
import { InMemoryMonitorsRepository } from "./InMemoryMonitorsRepository.ts";
import { InMemoryIncidentsRepository } from "./InMemoryIncidentsRepository.ts";
import { createMockLogger } from "./createMockLogger.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
import type { MonitorStatusResponse } from "../../src/types/network.ts";
import type { Check } from "../../src/domain/checks/check.type.ts";
import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts";
@@ -1,6 +1,6 @@
import { describe, expect, it } from "@jest/globals";
import type { IStatusProvider } from "../../src/service/network/IStatusProvider.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
/**
* Shared contract tests for all IStatusProvider implementations.
@@ -5,7 +5,7 @@ import { createMockLogger } from "../helpers/createMockLogger.ts";
import type { IMonitorsRepository } from "../../src/domain/monitors/monitor.repository.interface.ts";
import type { IUsersRepository } from "../../src/domain/users/user.repository.interface.ts";
import type { INotificationMessageBuilder } from "../../src/domain/notifications/notification.message-builder.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../src/domain/monitors/monitor.type.ts";
import type { MonitorActionDecision } from "../../src/worker/worker.helper.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
import { describe, expect, it, jest } from "@jest/globals";
import { AdvancedMatcher } from "../../../../src/service/network/AdvancedMatcher.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
import { DNSProvider } from "../../../../src/service/network/DNSProvider.ts";
// ── Mocks ────────────────────────────────────────────────────────────────────
@@ -2,8 +2,8 @@ import { describe, expect, it, jest } from "@jest/globals";
import { DockerProvider } from "../../../../src/service/network/DockerProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -2,8 +2,8 @@ import { describe, expect, it, jest } from "@jest/globals";
import { GameProvider } from "../../../../src/service/network/GameProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from "@jest/globals";
import { GrpcProvider } from "../../../../src/service/network/GrpcProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -2,7 +2,7 @@ import { describe, expect, it, jest } from "@jest/globals";
import { HardwareProvider } from "../../../../src/service/network/HardwareProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from "@jest/globals";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Mocks ────────────────────────────────────────────────────────────────────
@@ -4,7 +4,7 @@ import { testStatusProviderContract } from "../../../helpers/statusProviderContr
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts";
import type { ISettingsService } from "../../../../src/domain/app-settings/app-settings.service.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from "@jest/globals";
import { PingProvider } from "../../../../src/service/network/PingProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,8 +1,8 @@
import { describe, expect, it, jest } from "@jest/globals";
import { PortProvider } from "../../../../src/service/network/PortProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,5 +1,6 @@
import { describe, expect, it } from "@jest/globals";
import { timeRequest, NETWORK_ERROR, PING_ERROR, isStatusUp } from "../../../../src/service/network/utils.ts";
import { timeRequest, isStatusUp } from "../../../../src/service/network/utils.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
describe("network utils", () => {
describe("timeRequest", () => {
@@ -36,10 +37,6 @@ describe("network utils", () => {
it("NETWORK_ERROR is 5000", () => {
expect(NETWORK_ERROR).toBe(5000);
});
it("PING_ERROR is 5001", () => {
expect(PING_ERROR).toBe(5001);
});
});
describe("isStatusUp", () => {
@@ -1,8 +1,8 @@
import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals";
import { WebSocketProvider } from "../../../../src/service/network/WebSocketProvider.ts";
import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts";
import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts";
import { NETWORK_ERROR } from "../../../../src/types/network.ts";
import type { Monitor } from "../../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from "@jest/globals";
import { CheckEvaluator } from "../../../src/worker/worker.check-evaluator.ts";
import { MonitorStatusPolicy } from "../../../src/worker/worker.monitor-status-policy.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
({
@@ -1,6 +1,6 @@
import { describe, expect, it, jest } from "@jest/globals";
import { GeoChecksPipeline } from "../../../src/worker/worker.check-pipeline.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
@@ -1,6 +1,6 @@
import { describe, expect, it, jest } from "@jest/globals";
import { CheckProducer } from "../../../src/worker/worker.check-producer.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
const makeMonitor = (overrides?: Partial<Monitor>): Monitor =>
@@ -5,7 +5,7 @@ import type { IChecksRepository } from "../../../src/domain/checks/check.reposit
import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts";
import type { MonitorStatusResponse, HardwareStatusPayload, PageSpeedStatusPayload } from "../../../src/types/network.ts";
import { NotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -375,7 +375,7 @@ describe("CheckService", () => {
rowsPerPage: null as any,
});
expect(checksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "desc", "day", undefined, 0, 5, undefined);
expect(checksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "desc", "day", 0, 5, undefined, undefined);
});
it("passes filter and status parameters", async () => {
@@ -388,11 +388,11 @@ describe("CheckService", () => {
dateRange: "week",
page: 2,
rowsPerPage: 20,
filter: "error",
filter: "resolve",
status: false,
});
expect(checksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "asc", "week", "error", 2, 20, false);
expect(checksRepository.findByMonitorId).toHaveBeenCalledWith("mon-1", "asc", "week", 2, 20, false, "resolve");
});
it("throws when monitorId is missing", async () => {
@@ -443,7 +443,7 @@ describe("CheckService", () => {
await service.getChecksByTeam({ teamId: "team-1", sortOrder: "desc", dateRange: "day", page: null as any, rowsPerPage: null as any });
expect(checksRepository.findByTeamId).toHaveBeenCalledWith("desc", "day", undefined, 0, 5, "team-1");
expect(checksRepository.findByTeamId).toHaveBeenCalledWith("desc", "day", 0, 5, "team-1", undefined);
});
});
@@ -2,7 +2,7 @@ import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals
import { DBQueueWorker } from "../../../src/worker/worker.db-queue.ts";
import { LOCK_MS } from "../../../src/domain/jobs/job.type.ts";
import type { Job } from "../../../src/domain/jobs/job.type.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { QueueMode } from "../../../src/domain/app-settings/app-settings.type.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from "@jest/globals";
import { GeoChecksService } from "../../../src/domain/geo-checks/geo-check.service.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { GeoCheckResult } from "../../../src/domain/geo-checks/geo-check.type.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -5,7 +5,7 @@ import type { IIncidentsRepository } from "../../../src/domain/incidents/inciden
import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts";
import type { IUsersRepository } from "../../../src/domain/users/user.repository.interface.ts";
import type { INotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { Incident } from "../../../src/domain/incidents/incident.type.ts";
import type { MonitorActionDecision } from "../../../src/worker/worker.helper.ts";
@@ -1,6 +1,6 @@
import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals";
import { JobScheduler } from "../../../src/worker/worker.job-scheduler.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { QueueMode } from "../../../src/domain/app-settings/app-settings.type.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
@@ -574,7 +574,7 @@ describe("MonitorService", () => {
const monitorsRepository = createMonitorsRepositoryMock();
(monitorsRepository.findMonitorsSummaryByTeamId as jest.Mock).mockResolvedValue(null);
(monitorsRepository.findMonitorCountByTeamIdAndType as jest.Mock).mockResolvedValue(0);
(monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue(null);
(monitorsRepository.findByTeamIdWithStats as jest.Mock).mockResolvedValue([]);
const { service } = createService({ monitorsRepository });
const result = await service.getMonitorsWithChecksByTeamId({ teamId: TEAM_ID });
@@ -1576,14 +1576,6 @@ describe("MonitorService", () => {
expect(result).toEqual(monitors);
});
it("throws when no monitors found (null)", async () => {
const monitorsRepository = createMonitorsRepositoryMock();
(monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue(null);
const { service } = createService({ monitorsRepository });
await expect(service.exportMonitorsToJSON({ teamId: TEAM_ID })).rejects.toThrow("No monitors found to export.");
});
it("throws when monitors array is empty", async () => {
const monitorsRepository = createMonitorsRepositoryMock();
(monitorsRepository.findByTeamId as jest.Mock).mockResolvedValue([]);
@@ -1,9 +1,9 @@
import { describe, expect, it, jest } from "@jest/globals";
import { NetworkService } from "../../../src/service/networkService.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
import { NETWORK_ERROR } from "../../../src/service/network/utils.ts";
import { NETWORK_ERROR } from "../../../src/types/network.ts";
import type { IStatusProvider } from "../../../src/service/network/IStatusProvider.ts";
import type { Monitor, MonitorType } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor, MonitorType } from "../../../src/domain/monitors/monitor.type.ts";
import type { AxiosStatic } from "axios";
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
import { describe, expect, it, beforeEach } from "@jest/globals";
import { NotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/network.ts";
import type { MonitorActionDecision } from "../../../src/worker/worker.helper.ts";
@@ -1,7 +1,7 @@
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
import { NotificationsService } from "../../../src/domain/notifications/notification.service.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { Notification } from "../../../src/domain/notifications/notification.type.ts";
import type { MonitorStatusResponse } from "../../../src/types/network.ts";
import type { MonitorActionDecision } from "../../../src/worker/worker.helper.ts";
@@ -3,7 +3,7 @@ import { StatusPageService } from "../../../src/domain/status-pages/status-page.
import type { IStatusPagesRepository } from "../../../src/domain/status-pages/status-page-repository.interface.ts";
import type { ISettingsService } from "../../../src/domain/app-settings/app-settings.service.ts";
import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { StatusPage } from "../../../src/domain/status-pages/status-page.type.ts";
import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/domain/status-pages/status-page.type.ts";
@@ -1,8 +1,8 @@
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
import { StatusService } from "../../../src/service/statusService.ts";
import { createMockLogger } from "../../helpers/createMockLogger.ts";
import { MAX_RECENT_CHECKS } from "../../../src/domain/monitors/monitor.types.ts";
import type { Monitor, MonitorStatus } from "../../../src/domain/monitors/monitor.types.ts";
import { MAX_RECENT_CHECKS } from "../../../src/domain/monitors/monitor.type.ts";
import type { Monitor, MonitorStatus } from "../../../src/domain/monitors/monitor.type.ts";
import type { Check } from "../../../src/domain/checks/check.type.ts";
import type { MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/network.ts";
import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts";