resolve remaining TS errors

This commit is contained in:
Alex Holliday committed 2026-03-03 19:34:28 +00:00
1 parent a0ea50d745
commit f79a87f98a
3 files changed
+280 -123

No files matched your search

+16 -1
View File
@@ -145,7 +145,22 @@ export const initializeServices = async ({
const teamsRepository = new MongoTeamsRepository();
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
const networkService = new NetworkService(axios, got, https, jmespath, GameDig, ping, logger, Docker, net, settingsService, grpc, protoLoader);
const networkService = new NetworkService(
axios,
got,
https,
jmespath,
GameDig as unknown as {
query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }>;
},
ping,
logger,
Docker,
net,
settingsService,
grpc,
protoLoader
);
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
const notificationMessageBuilder = new NotificationMessageBuilder();
@@ -7,6 +7,10 @@ import type {
PageSpeedStatusPayload,
HttpStatusPayload,
HardwareStatusPayload,
PingStatusPayload,
DockerStatusPayload,
PortStatusPayload,
GameStatusPayload,
} from "@/types/index.js";
import type { AxiosStatic } from "axios";
import { AppError } from "@/utils/AppError.js";
@@ -34,8 +38,25 @@ interface BuildStatusResponseArgs<T> {
export interface INetworkService {
readonly serviceName: string;
requestStatus(monitor: Monitor): Promise<MonitorStatusResponse>;
requestWebhook(type: string, url: string, body: any): Promise<{ type: string; status: boolean; code: number; message: string; payload?: unknown }>;
requestStatus(
monitor: Monitor
): Promise<
MonitorStatusResponse<
| PingStatusPayload
| HttpStatusPayload
| PageSpeedStatusPayload
| HardwareStatusPayload
| DockerStatusPayload
| PortStatusPayload
| GameStatusPayload
| GrpcStatusPayload
>
>;
requestWebhook(
type: string,
url: string,
body: unknown
): Promise<{ type: string; status: boolean; code: number; message: string; payload?: unknown }>;
requestPagerDuty(args: { message: string; routingKey: string; monitorUrl: string }): Promise<boolean>;
requestMatrix(args: { homeserverUrl: string; accessToken: string; roomId: string; message: string }): Promise<{
status: boolean;
@@ -62,16 +83,16 @@ class NetworkService implements INetworkService {
private axios: AxiosStatic;
private got: Got;
private https: any;
private jmespath: any;
private GameDig: any;
private ping: any;
private https: typeof import("https");
private jmespath: { search: (data: unknown, expression: string) => unknown };
private GameDig: { query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }> };
private ping: typeof import("ping");
private logger: ILogger;
private Docker: any;
private net: any;
private Docker: typeof import("dockerode");
private net: typeof import("net");
private settingsService: ISettingsService;
private grpc: any;
private protoLoader: any;
private grpc: typeof import("@grpc/grpc-js");
private protoLoader: typeof import("@grpc/proto-loader");
private buildStatusResponse = <T>({
monitor,
@@ -106,7 +127,7 @@ class NetworkService implements INetworkService {
statusResponse.responseTime = error.timings?.phases?.total ?? 0;
statusResponse.timings = error.timings;
}
return { ...statusResponse, ...(overrides ?? {}) };
return { ...statusResponse, ...(overrides ?? {}) } as MonitorStatusResponse<T>;
}
return {
@@ -124,22 +145,22 @@ class NetworkService implements INetworkService {
expectedValue,
extracted,
...(overrides ?? {}),
};
} as MonitorStatusResponse<T>;
};
constructor(
axios: AxiosStatic,
got: Got,
https: any,
jmespath: any,
GameDig: any,
ping: any,
https: typeof import("https"),
jmespath: { search: (data: unknown, expression: string) => unknown },
GameDig: { query: (options: { type: string; host: string; port?: number }) => Promise<{ ping?: number } & { [key: string]: unknown }> },
ping: typeof import("ping"),
logger: ILogger,
Docker: any,
net: any,
Docker: typeof import("dockerode"),
net: typeof import("net"),
settingsService: ISettingsService,
grpc: any,
protoLoader: any
grpc: typeof import("@grpc/grpc-js"),
protoLoader: typeof import("@grpc/proto-loader")
) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
@@ -193,7 +214,20 @@ class NetworkService implements INetworkService {
}
// Main entry point
async requestStatus(monitor: Monitor): Promise<MonitorStatusResponse> {
async requestStatus(
monitor: Monitor
): Promise<
MonitorStatusResponse<
| PingStatusPayload
| HttpStatusPayload
| PageSpeedStatusPayload
| HardwareStatusPayload
| DockerStatusPayload
| PortStatusPayload
| GameStatusPayload
| GrpcStatusPayload
>
> {
const type = monitor?.type || "unknown";
switch (type) {
case this.TYPE_PING:
@@ -217,7 +251,7 @@ class NetworkService implements INetworkService {
}
}
private async requestPing(monitor: Monitor): Promise<MonitorStatusResponse> {
private async requestPing(monitor: Monitor): Promise<MonitorStatusResponse<PingStatusPayload>> {
try {
if (!monitor?.url) {
throw new Error("Monitor URL is required");
@@ -237,30 +271,27 @@ class NetworkService implements INetworkService {
throw new Error("Ping failed - no result returned");
}
const pingResponse = this.buildStatusResponse({
const pingResponse = this.buildStatusResponse<PingStatusPayload>({
monitor,
payload: response,
payload: response as PingStatusPayload,
overrides: {
status: (response as { alive?: boolean })?.alive ?? false,
code: 200,
message: "Success",
responseTime: (response as { time?: number })?.time ?? 0,
payload: response,
},
payload: response as PingStatusPayload,
} as MonitorStatusResponseOverrides<PingStatusPayload>,
});
if (error) {
pingResponse.status = false;
pingResponse.code = this.PING_ERROR;
pingResponse.message = "Ping failed";
return pingResponse;
}
return pingResponse;
} catch (err: any) {
err.service = this.SERVICE_NAME;
err.method = "requestPing";
throw err;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing ping",
service: this.SERVICE_NAME,
method: "requestPing",
details: { url: monitor.url },
});
}
}
@@ -279,7 +310,7 @@ class NetworkService implements INetworkService {
if (!url) {
throw new Error("Monitor URL is required");
}
const config: Record<string, any> = {
const config: Record<string, unknown> = {
headers: secret ? { Authorization: `Bearer ${secret}` } : undefined,
};
@@ -293,7 +324,7 @@ class NetworkService implements INetworkService {
const response = await this.got(url, config);
let payload: any;
let payload: T | string | undefined;
const contentType = response.headers["content-type"];
if (contentType && contentType.includes("application/json")) {
@@ -381,19 +412,23 @@ class NetworkService implements INetworkService {
}
}
return httpResponse;
} catch (err: any) {
if (err.name === "HTTPError" || err.name === "RequestError") {
httpResponse.code = err?.response?.statusCode || this.NETWORK_ERROR;
} catch (err: unknown) {
if (err instanceof HTTPError || err instanceof RequestError) {
httpResponse.code = err.response?.statusCode || this.NETWORK_ERROR;
httpResponse.status = false;
httpResponse.message = err?.response?.statusCode || err.message;
httpResponse.responseTime = err?.timings?.phases?.total || 0;
httpResponse.message = String(err.response?.statusCode) || err.message;
httpResponse.responseTime = err.timings?.phases?.total || 0;
httpResponse.payload = null;
httpResponse.timings = err?.timings || {};
httpResponse.timings = err.timings || undefined;
return httpResponse;
}
err.service = this.SERVICE_NAME;
err.method = "requestHttp";
throw err;
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing HTTP request",
service: this.SERVICE_NAME,
method: "requestHttp",
details: { url: monitor.url },
});
}
}
@@ -419,24 +454,32 @@ class NetworkService implements INetworkService {
...monitor,
url: pageSpeedUrl,
});
} catch (err: any) {
err.service = this.SERVICE_NAME;
err.method = "requestPageSpeed";
throw err;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing PageSpeed request",
service: this.SERVICE_NAME,
method: "requestPageSpeed",
details: { url: monitor.url },
});
}
}
private async requestHardware(monitor: Monitor): Promise<MonitorStatusResponse<HardwareStatusPayload>> {
try {
return await this.requestHttp<HardwareStatusPayload>(monitor);
} catch (err: any) {
err.service = this.SERVICE_NAME;
err.method = "requestHardware";
throw err;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing hardware request",
service: this.SERVICE_NAME,
method: "requestHardware",
details: { url: monitor.url },
});
}
}
private async requestDocker(monitor: Monitor): Promise<MonitorStatusResponse> {
private async requestDocker(monitor: Monitor): Promise<MonitorStatusResponse<DockerStatusPayload>> {
try {
if (!monitor.url) {
throw new Error("Monitor URL is required");
@@ -446,7 +489,7 @@ class NetworkService implements INetworkService {
socketPath: "/var/run/docker.sock",
});
const dockerResponse = this.buildStatusResponse({
const dockerResponse = this.buildStatusResponse<DockerStatusPayload>({
monitor,
overrides: {
status: false,
@@ -455,17 +498,17 @@ class NetworkService implements INetworkService {
},
});
const containers = await docker.listContainers({ all: true });
const containers = await docker.listContainers();
// Normalize input: strip leading slashes and convert to lowercase for comparison
const normalizedInput = monitor.url.replace(/^\/+/, "").toLowerCase();
// Priority-based matching to avoid ambiguity:
// 1. Exact full ID match (64-char)
const exactIdMatch = containers.find((c: any) => c.Id.toLowerCase() === normalizedInput);
const exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
// 2. Exact container name match (case-insensitive)
const exactNameMatch = containers.find((c: any) =>
const exactNameMatch = containers.find((c) =>
c.Names.some((name: string) => {
const cleanName = name.replace(/^\/+/, "").toLowerCase();
return cleanName === normalizedInput;
@@ -473,7 +516,7 @@ class NetworkService implements INetworkService {
);
// 3. Partial ID match (fallback for backwards compatibility)
const partialIdMatch = containers.find((c: any) => c.Id.toLowerCase().startsWith(normalizedInput));
const partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
// Select container based on priority
const targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
@@ -512,9 +555,8 @@ class NetworkService implements INetworkService {
}
const container = docker.getContainer(targetContainer.Id);
const { response, responseTime, error }: { response?: any; responseTime?: number; error?: any } = await this.timeRequest(() =>
container.inspect()
);
const { response, responseTime, error }: { response?: { State: { Status: string } } | null; responseTime: number; error: unknown } =
await this.timeRequest(() => container.inspect());
dockerResponse.responseTime = responseTime;
dockerResponse.status = response?.State?.Status === "running" ? true : false;
@@ -523,20 +565,28 @@ class NetworkService implements INetworkService {
if (error) {
dockerResponse.status = false;
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;
dockerResponse.message = error.reason || "Failed to fetch Docker container information";
dockerResponse.code =
(error && typeof error === "object" && "statusCode" in error ? (error as { statusCode?: number }).statusCode : undefined) ||
this.NETWORK_ERROR;
dockerResponse.message =
(error && typeof error === "object" && "reason" in error ? (error as { reason?: string }).reason : undefined) ||
"Failed to fetch Docker container information";
return dockerResponse;
}
return dockerResponse;
} catch (err: any) {
err.service = this.SERVICE_NAME;
err.method = "requestDocker";
throw err;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing Docker request",
service: this.SERVICE_NAME,
method: "requestDocker",
details: { url: monitor.url },
});
}
}
private async requestPort(monitor: Monitor): Promise<MonitorStatusResponse> {
private async requestPort(monitor: Monitor): Promise<MonitorStatusResponse<PortStatusPayload>> {
try {
const { url, port } = monitor;
if (!port) {
@@ -563,14 +613,14 @@ class NetworkService implements INetworkService {
reject(new Error("Connection timeout"));
});
socket.on("error", (err: any) => {
socket.on("error", (err: unknown) => {
socket.destroy();
reject(err);
});
});
});
const portResponse = this.buildStatusResponse({
const portResponse = this.buildStatusResponse<PortStatusPayload>({
monitor,
overrides: {
code: 200,
@@ -588,18 +638,22 @@ class NetworkService implements INetworkService {
}
return portResponse;
} catch (error: any) {
error.service = this.SERVICE_NAME;
error.method = "requestTCP";
throw error;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing port check",
service: this.SERVICE_NAME,
method: "requestPort",
details: { url: monitor.url, port: monitor.port },
});
}
}
private async requestGame(monitor: Monitor): Promise<MonitorStatusResponse> {
private async requestGame(monitor: Monitor): Promise<MonitorStatusResponse<GameStatusPayload>> {
try {
const { url, port, gameId } = monitor;
const gameResponse = this.buildStatusResponse({
const gameResponse = this.buildStatusResponse<GameStatusPayload>({
monitor,
overrides: {
code: 200,
@@ -612,13 +666,14 @@ class NetworkService implements INetworkService {
type: gameId ?? "unknown",
host: url,
port: port ?? 0,
}).catch((error: any) => {
}).catch((error: unknown): undefined => {
this.logger.warn({
message: error.message,
message: error instanceof Error ? error.message : String(error),
service: this.SERVICE_NAME,
method: "requestGame",
details: { url, port, gameId },
});
return undefined;
});
if (!state) {
@@ -628,16 +683,20 @@ class NetworkService implements INetworkService {
return gameResponse;
}
gameResponse.responseTime = state.ping;
gameResponse.payload = state;
gameResponse.responseTime = state.ping ?? 0;
gameResponse.payload = state as GameStatusPayload;
return gameResponse;
} catch (error: any) {
error.service = this.SERVICE_NAME;
error.method = "requestPing";
throw error;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing game server check",
service: this.SERVICE_NAME,
method: "requestGame",
details: { url: monitor.url, port: monitor.port, gameId: monitor.gameId },
});
}
}
private async requestGrpc(monitor: Monitor): Promise<MonitorStatusResponse> {
private async requestGrpc(monitor: Monitor): Promise<MonitorStatusResponse<GrpcStatusPayload>> {
try {
const { url, port, ignoreTlsErrors } = monitor;
const grpcServiceName = monitor.grpcServiceName || "";
@@ -661,7 +720,21 @@ class NetworkService implements INetworkService {
defaults: true,
oneofs: true,
});
const grpcObject = this.grpc.loadPackageDefinition(packageDefinition);
const grpcObject = this.grpc.loadPackageDefinition(packageDefinition) as unknown as {
grpc: {
health: {
v1: {
Health: new (
target: string,
credentials: unknown
) => {
Check: (request: { service: string }, options: { deadline: Date }, callback: (err: unknown, response: unknown) => void) => void;
close: () => void;
};
};
};
};
};
const healthService = grpcObject.grpc.health.v1.Health;
let credentials;
@@ -678,7 +751,7 @@ class NetworkService implements INetworkService {
const TIMEOUT_MS = 10000;
const deadline = new Date(Date.now() + TIMEOUT_MS);
const grpcResponse = this.buildStatusResponse({
const grpcResponse = this.buildStatusResponse<GrpcStatusPayload>({
monitor,
overrides: {
status: false,
@@ -689,24 +762,30 @@ class NetworkService implements INetworkService {
const { response, responseTime, error } = await this.timeRequest<GrpcStatusPayload>(() => {
return new Promise<GrpcStatusPayload>((resolve, reject) => {
client.Check({ service: grpcServiceName }, { deadline }, (err: any, response: any) => {
client.Check({ service: grpcServiceName }, { deadline }, (err: unknown, response: unknown) => {
client.close();
if (err) {
const grpcErr = err as { code?: number; details?: string; message?: string };
const payload: GrpcStatusPayload = {
grpcStatusCode: err.code ?? -1,
grpcStatusName: this.getGrpcStatusName(err.code),
grpcStatusCode: grpcErr.code ?? -1,
grpcStatusName: this.getGrpcStatusName(grpcErr.code ?? -1),
serviceName: grpcServiceName,
servingStatus: "UNKNOWN",
};
const grpcError = new AppError({ message: err.details || err.message, service: this.SERVICE_NAME, method: "requestGrpc" });
(grpcError as any).grpcPayload = payload;
(grpcError as any).grpcCode = err.code;
const grpcError = new AppError({
message: grpcErr.details || grpcErr.message || "gRPC error",
service: this.SERVICE_NAME,
method: "requestGrpc",
}) as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
grpcError.grpcPayload = payload;
grpcError.grpcCode = grpcErr.code;
reject(grpcError);
return;
}
const servingStatus = response?.status || "UNKNOWN";
const resp = response as { status?: string } | undefined;
const servingStatus = resp?.status ?? "UNKNOWN";
resolve({
grpcStatusCode: 0,
grpcStatusName: "OK",
@@ -718,13 +797,13 @@ class NetworkService implements INetworkService {
});
if (error) {
const grpcError = error as any;
const payload = grpcError.grpcPayload as GrpcStatusPayload | undefined;
const grpcError = error as AppError & { grpcPayload?: GrpcStatusPayload; grpcCode?: number };
const payload = grpcError.grpcPayload;
grpcResponse.status = false;
grpcResponse.code = grpcError.grpcCode ?? this.NETWORK_ERROR;
grpcResponse.message = grpcError.message || "gRPC health check failed";
grpcResponse.message = grpcError.message ?? "gRPC health check failed";
grpcResponse.responseTime = responseTime;
grpcResponse.payload = payload || null;
grpcResponse.payload = payload ?? null;
return grpcResponse;
}
@@ -740,10 +819,14 @@ class NetworkService implements INetworkService {
grpcResponse.payload = grpcPayload;
return grpcResponse;
} catch (err: any) {
err.service = this.SERVICE_NAME;
err.method = "requestGrpc";
throw err;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error performing gRPC health check",
service: this.SERVICE_NAME,
method: "requestGrpc",
details: { url: monitor.url, port: monitor.port, grpcServiceName: monitor.grpcServiceName },
});
}
}
@@ -782,7 +865,7 @@ class NetworkService implements INetworkService {
}
// Other network requests unrelated to monitoring:
async requestWebhook(type: string, url: string, body: any) {
async requestWebhook(type: string, url: string, body: unknown) {
try {
const response = await this.axios.post(url, body, {
headers: {
@@ -797,19 +880,29 @@ class NetworkService implements INetworkService {
message: `Successfully sent ${type} notification`,
payload: response.data,
};
} catch (error: any) {
} catch (err: unknown) {
this.logger.warn({
message: error.message,
message: err instanceof Error ? err.message : String(err),
service: this.SERVICE_NAME,
method: "requestWebhook",
});
if (err && typeof err === "object" && "response" in err) {
const axiosError = err as { response?: { status?: number; data?: unknown } };
return {
type: "webhook",
status: false,
code: axiosError.response?.status ?? this.NETWORK_ERROR,
message: `Failed to send ${type} notification`,
payload: axiosError.response?.data,
};
}
return {
type: "webhook",
status: false,
code: error.response?.status || this.NETWORK_ERROR,
code: this.NETWORK_ERROR,
message: `Failed to send ${type} notification`,
payload: error.response?.data,
};
}
}
@@ -829,11 +922,17 @@ class NetworkService implements INetworkService {
if (response?.data?.status !== "success") return false;
return true;
} catch (error: any) {
error.details = error.response?.data;
error.service = this.SERVICE_NAME;
error.method = "requestPagerDuty";
throw error;
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error sending PagerDuty notification",
service: this.SERVICE_NAME,
method: "requestPagerDuty",
details: {
responseData: err && typeof err === "object" && "response" in err ? (err as { response?: { data?: unknown } }).response?.data : undefined,
},
});
}
}
@@ -867,18 +966,35 @@ class NetworkService implements INetworkService {
code: response.status,
message: "Successfully sent Matrix notification",
};
} catch (error: any) {
} catch (err: unknown) {
if (err instanceof Error) {
this.logger.warn({
message: err.message,
service: this.SERVICE_NAME,
method: "requestMatrix",
});
if (err && typeof err === "object" && "response" in err) {
const axiosError = err as { response?: { status?: number; data?: unknown } };
return {
status: false,
code: axiosError.response?.status || this.NETWORK_ERROR,
message: "Failed to send Matrix notification",
payload: axiosError.response?.data,
};
}
}
this.logger.warn({
message: error.message,
message: String(err),
service: this.SERVICE_NAME,
method: "requestMatrix",
});
return {
status: false,
code: error.response?.status || this.NETWORK_ERROR,
code: this.NETWORK_ERROR,
message: "Failed to send Matrix notification",
payload: error.response?.data,
};
}
}
@@ -7,6 +7,12 @@ import type {
Check,
HardwareStatusPayload,
PageSpeedStatusPayload,
PingStatusPayload,
HttpStatusPayload,
DockerStatusPayload,
PortStatusPayload,
GameStatusPayload,
GrpcStatusPayload,
CheckSnapshot,
MonitorStats,
} from "@/types/index.js";
@@ -17,7 +23,17 @@ export interface IStatusService {
updateRunningStats({ monitor, networkResponse }: { monitor: Monitor; networkResponse: any }): Promise<boolean>;
handleIncidentForCheck(check: any, monitor: Monitor, action: any, errorContext?: string): Promise<void>;
updateMonitorStatus(
statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>,
statusResponse: MonitorStatusResponse<
| PingStatusPayload
| HttpStatusPayload
| PageSpeedStatusPayload
| HardwareStatusPayload
| DockerStatusPayload
| PortStatusPayload
| GameStatusPayload
| GrpcStatusPayload
| undefined
>,
check: Check
): Promise<StatusChangeResult>;
}
@@ -205,7 +221,17 @@ export class StatusService implements IStatusService {
};
updateMonitorStatus = async (
statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>,
statusResponse: MonitorStatusResponse<
| PingStatusPayload
| HttpStatusPayload
| PageSpeedStatusPayload
| HardwareStatusPayload
| DockerStatusPayload
| PortStatusPayload
| GameStatusPayload
| GrpcStatusPayload
| undefined
>,
check: Check
): Promise<StatusChangeResult> => {
try {