Files
Checkmate/server/src/service/infrastructure/notificationsService.ts
T

150 lines
5.8 KiB
TypeScript
Raw Normal View History

2026-01-19 00:48:20 +00:00
import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification } from "@/types/index.js";
2026-01-19 18:58:17 +00:00
import { shouldSendHardwareAlert } from "@/service/infrastructure/notificationProviders/utils.js";
2026-01-19 00:48:20 +00:00
import { INotificationsRepository } from "@/repositories/index.js";
2026-01-19 21:39:02 +00:00
import { INotificationProvider } from "./notificationProviders/INotificationProvider.js";
2026-01-18 22:52:59 +00:00
export interface INotificationsService {
2026-01-18 23:38:40 +00:00
handleNotifications: (
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
prevStatus: boolean | undefined,
statusChanged: boolean
) => Promise<boolean>;
2026-01-19 21:39:02 +00:00
sendTestNotification: (notification: Notification) => Promise<boolean>;
testAllNotifications: (notificationIds: string[]) => Promise<boolean>;
2026-01-18 22:52:59 +00:00
}
2026-01-19 21:39:02 +00:00
const SERVICE_NAME = "NotificationsService";
2026-01-18 22:52:59 +00:00
export class NotificationsService implements INotificationsService {
2026-01-19 21:39:02 +00:00
static SERVICE_NAME = SERVICE_NAME;
2026-01-19 00:48:20 +00:00
private notificationsRepository: INotificationsRepository;
2026-01-19 21:39:02 +00:00
private webhookProvider: INotificationProvider;
private emailProvider: INotificationProvider;
private slackProvider: INotificationProvider;
private discordProvider: INotificationProvider;
private pagerDutyProvider: INotificationProvider;
private matrixProvider: INotificationProvider;
private logger: any;
2026-01-19 18:58:17 +00:00
2026-01-19 19:30:46 +00:00
constructor(
notificationsRepository: INotificationsRepository,
2026-01-19 21:39:02 +00:00
webhookProvider: INotificationProvider,
emailProvider: INotificationProvider,
slackProvider: INotificationProvider,
discordProvider: INotificationProvider,
pagerDutyProvider: INotificationProvider,
matrixProvider: INotificationProvider,
logger: any
2026-01-19 19:30:46 +00:00
) {
2026-01-19 00:48:20 +00:00
this.notificationsRepository = notificationsRepository;
2026-01-19 21:39:02 +00:00
this.webhookProvider = webhookProvider;
this.emailProvider = emailProvider;
this.slackProvider = slackProvider;
this.discordProvider = discordProvider;
this.pagerDutyProvider = pagerDutyProvider;
this.matrixProvider = matrixProvider;
this.logger = logger;
2026-01-19 00:48:20 +00:00
}
2026-01-19 18:58:17 +00:00
private send = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise<boolean> => {
switch (notification.type) {
2026-01-19 19:30:46 +00:00
case "email":
return await this.emailProvider.sendAlert(notification, monitor, monitorStatusResponse);
case "slack":
return await this.slackProvider.sendAlert(notification, monitor, monitorStatusResponse);
case "discord":
return await this.discordProvider.sendAlert(notification, monitor, monitorStatusResponse);
2026-01-19 21:39:02 +00:00
case "pager_duty":
return await this.pagerDutyProvider.sendAlert(notification, monitor, monitorStatusResponse);
case "matrix":
return await this.matrixProvider.sendAlert(notification, monitor, monitorStatusResponse);
2026-01-19 19:30:46 +00:00
case "webhook":
2026-01-19 18:58:17 +00:00
return await this.webhookProvider.sendAlert(notification, monitor, monitorStatusResponse);
2026-01-19 19:30:46 +00:00
default:
return false;
2026-01-19 18:58:17 +00:00
}
2026-01-19 00:48:20 +00:00
};
private sendNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
2026-01-19 18:58:17 +00:00
const notificationIds = monitor.notifications ?? [];
const notifications = await this.notificationsRepository.findNotificationsByIds(notificationIds);
const tasks = notifications.map((notification) => this.send(notification, monitor, monitorStatusResponse));
const outcomes = await Promise.all(tasks);
const succeeded = outcomes.filter(Boolean).length;
const failed = outcomes.length - succeeded;
if (failed > 0) {
2026-01-19 21:39:02 +00:00
this.logger.warn({
message: `Notification send completed with ${succeeded} success, ${failed} failure(s)`,
service: SERVICE_NAME,
method: "getMonitorJob",
});
2026-01-19 18:58:17 +00:00
}
// Return true if all notificaitons succeeded
return succeeded === notifications.length;
2026-01-19 00:48:20 +00:00
};
2026-01-18 23:38:40 +00:00
handleNotifications = async (
monitor: Monitor,
monitorStatusResponse: MonitorStatusResponse,
prevStatus: boolean | undefined,
statusChanged: boolean
) => {
const { type } = monitor;
const payload = monitorStatusResponse.payload as HardwareStatusPayload;
// If this is a non-hardeware type monitor and status did not change, we're done
if (type !== "hardware" && statusChanged === false) return false;
// if prevStatus is undefined, monitor is resuming, we're done
if (type !== "hardware" && prevStatus === undefined) return false;
// Deal with hardware thresholds
if (type === "hardware") {
const thresholds = monitor.thresholds;
if (thresholds === undefined) return false; // No thresholds set, we're done
const metrics = payload?.data ?? null;
if (metrics === null) return false; // No metrics, we're done
2026-01-19 18:58:17 +00:00
// We should send a notificaiton
2026-01-18 23:38:40 +00:00
2026-01-19 00:48:20 +00:00
const shouldSend = shouldSendHardwareAlert(monitor, monitorStatusResponse);
if (shouldSend === false) return false;
2026-01-19 21:39:02 +00:00
2026-01-19 00:48:20 +00:00
return await this.sendNotifications(monitor, monitorStatusResponse);
2026-01-18 23:38:40 +00:00
}
2026-01-19 00:48:20 +00:00
// We should send a notification for non-hardware monitor status change
return await this.sendNotifications(monitor, monitorStatusResponse);
2026-01-18 22:52:59 +00:00
};
2026-01-19 21:39:02 +00:00
sendTestNotification = async (notification: Notification) => {
switch (notification.type) {
case "email":
return await this.emailProvider.sendTestAlert(notification);
case "slack":
return await this.slackProvider.sendTestAlert(notification);
case "discord":
return await this.discordProvider.sendTestAlert(notification);
case "pager_duty":
return await this.pagerDutyProvider.sendTestAlert(notification);
case "matrix":
return await this.matrixProvider.sendTestAlert(notification);
case "webhook":
return await this.webhookProvider.sendTestAlert(notification);
default:
return false;
}
};
testAllNotifications = async (notificationIds: string[]) => {
const notifications = await this.notificationsRepository.findNotificationsByIds(notificationIds);
const tasks = notifications.map((notification) => this.sendTestNotification(notification));
const outcomes = await Promise.all(tasks);
const succeeded = outcomes.filter(Boolean).length;
const failed = outcomes.length - succeeded;
if (failed > 0) {
return false;
}
return true;
};
2026-01-18 06:02:07 +00:00
}