providers

This commit is contained in:
Alex Holliday
2026-01-19 18:58:17 +00:00
parent 7d6bc4b2dc
commit f7f764fd68
11 changed files with 126 additions and 24 deletions
@@ -2,6 +2,7 @@ import type { Notification } from "@/types/index.js";
export interface INotificationsRepository {
// create
// fetch
findNotificationsByIds(ids: string[]): Promise<Notification[]>;
// update
// delete
}
@@ -35,6 +35,12 @@ class MongoNotificationsRepository implements INotificationsRepository {
updatedAt: toDateString(doc.updatedAt),
};
};
findNotificationsByIds = async (ids: string[]) => {
const mongoIds = ids.map((id) => new mongoose.Types.ObjectId(id));
const documents = await NotificationModel.find({ _id: { $in: mongoIds } });
return this.mapDocuments(documents);
};
}
export default MongoNotificationsRepository;
+5 -1
View File
@@ -1,6 +1,10 @@
export * from "@/service/business/monitorService.js";
export * from "@/service/infrastructure/networkService.js";
export * from "@/service/infrastructure/notificationProviders/webhook.js";
export * from "@/service/infrastructure/notificationProviders/discord.js";
export * from "@/service/infrastructure/notificationProviders/email.js";
export * from "@/service/infrastructure/notificationProviders/INotificationProvider.js";
export * from "@/service/infrastructure/notificationProviders/slack.js";
export * from "@/service/infrastructure/notificationProviders/webhook.js";
export * from "@/service/infrastructure/notificationsService.js";
export * from "@/service/infrastructure/statusService.js";
export * from "@/service/infrastructure/notificationProviders/webhook.js";
@@ -83,7 +83,7 @@ class SuperSimpleQueueHelper {
// Step 4. Update monitor status
const statusChangeResult = await this.statusService.updateMonitorStatus(status, check);
// Step 5 handle notifications
// Step 5 handle notifications (best effort, continue even in event of failure, don't wait)
this.notificationsService.handleNotifications(monitor, status, statusChangeResult.prevStatus, statusChangeResult.statusChanged);
// this.notificationService
@@ -1,7 +1,6 @@
import type { Monitor, Notification, Alert, MonitorStatusResponse } from "@/types/index.js";
export interface INotificationProvider {
buildAlert: (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => Alert;
sendAlert: (alert: Alert, notification: Notification) => Promise<boolean>;
sendAlert: (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => Promise<boolean>;
sendTestAlert(notification: Notification): Promise<boolean>;
}
@@ -0,0 +1,10 @@
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
import { INotificationProvider } from "@/service/index.js";
export class DiscordProvider implements INotificationProvider {
sendAlert = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
return false;
};
sendTestAlert = async (notification: Notification) => {
return false;
};
}
@@ -0,0 +1,12 @@
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
import { INotificationProvider } from "@/service/index.js";
export class EmailProvider implements INotificationProvider {
async sendAlert(notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise<boolean> {
return false;
}
async sendTestAlert(notification: Notification): Promise<boolean> {
return false;
}
}
@@ -0,0 +1,12 @@
import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js";
import { INotificationProvider } from "@/service/index.js";
export class SlackProvider implements INotificationProvider {
async sendAlert(notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise<boolean> {
return false;
}
async sendTestAlert(notification: Notification): Promise<boolean> {
return false;
}
}
@@ -93,13 +93,19 @@ export const buildHardwareAlerts = (
return { alertsToSend, discordPayload };
};
const buildHardwareNotificationMessage = (clientHost: string, alerts: any, monitor: Monitor) => {
export const buildHardwareNotificationMessage = (clientHost: string, alerts: any, monitor: Monitor) => {
const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`];
const alertFooter = [`Go to incident: ${clientHost}/infrastructure/${monitor.id}`];
const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts, ...alertFooter] : [];
return alertText.map((alert) => alert).join("\n");
};
export const buildHardwareWebhookBody = (alerts: string[], monitor: Monitor): { text: string; name: string; url: string } => {
const content = alerts.map((alert) => alert).join("\n");
const body = { text: content, name: monitor.name, url: monitor.url };
return body;
};
export const shouldSendHardwareAlert = (monitor: Monitor, networkResponse: MonitorStatusResponse): boolean => {
const thresholds = monitor.thresholds || {};
const { usage_cpu: cpuThreshold = -1, usage_memory: memoryThreshold = -1, usage_disk: diskThreshold = -1 } = thresholds;
@@ -125,3 +131,9 @@ export const shouldSendHardwareAlert = (monitor: Monitor, networkResponse: Monit
return false;
};
export const buildWebhookBody = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
const { status, code } = monitorStatusResponse;
const formattedTime = Date.now().toLocaleString();
return `Monitor: ${monitor.name}\nTime: ${formattedTime}\nStatus: ${status ? "UP" : "DOWN"}\n Status Code: ${code}\n\u200B\n`;
};
@@ -1,15 +1,47 @@
import type { Monitor, Alert, Notification, MonitorStatusResponse } from "@/types/index.js";
import { INotificationProvider } from "@/service/index.js";
import { buildHardwareAlerts, buildHardwareWebhookBody, buildWebhookBody } from "@/service/infrastructure/notificationProviders/utils.js";
import got from "got";
export class WebhookProvider implements INotificationProvider {
buildAlert = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
return null;
private getHardwareContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
const { alertsToSend } = buildHardwareAlerts("HOST_PLACEHOLDER", monitor, monitorStatusResponse);
const body = buildHardwareWebhookBody(alertsToSend, monitor);
return body;
};
sendAlert = async (alert: Alert) => {
return false;
private getContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
const body = buildWebhookBody(monitor, monitorStatusResponse);
return body;
};
sendTestAlert(notification: Notification): Promise<boolean> {
sendAlert = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
let body;
if (monitor.type === "hardware") {
body = this.getHardwareContent(monitor, monitorStatusResponse);
} else {
body = this.getContent(monitor, monitorStatusResponse);
}
if (!notification.address) {
return false;
}
try {
await got.post(notification.address, {
json: body,
headers: {
"Content-Type": "application/json",
},
responseType: "json",
});
return true;
} catch (error) {
return false;
}
};
sendTestAlert = async (notification: Notification) => {
return false;
}
};
}
@@ -1,7 +1,7 @@
import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification } from "@/types/index.js";
import { NotificationModel } from "@/db/models/index.js";
import { buildHardwareAlerts, shouldSendHardwareAlert } from "@/service/infrastructure/notificationProviders/utils.js";
import { shouldSendHardwareAlert } from "@/service/infrastructure/notificationProviders/utils.js";
import { INotificationsRepository } from "@/repositories/index.js";
import { WebhookProvider } from "@/service/index.js";
export interface INotificationsService {
handleNotifications: (
monitor: Monitor,
@@ -13,21 +13,36 @@ export interface INotificationsService {
export class NotificationsService implements INotificationsService {
private notificationsRepository: INotificationsRepository;
constructor(notificationsRepository: INotificationsRepository) {
private webhookProvider: WebhookProvider;
constructor(notificationsRepository: INotificationsRepository, webhookProvider: WebhookProvider) {
this.notificationsRepository = notificationsRepository;
this.webhookProvider = webhookProvider;
}
private send = (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
console.log(notification);
private send = async (notification: Notification, monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): Promise<boolean> => {
switch (notification.type) {
case " email": {
}
case "webhook": {
return await this.webhookProvider.sendAlert(notification, monitor, monitorStatusResponse);
}
}
return false;
};
private sendNotifications = async (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => {
const notifications = await this.notificationsRepository.findByMonitorId(monitor.id);
console.log({ notifications });
const tasks = notifications.map((notification) => {
this.send(notification, monitor, monitorStatusResponse);
});
return true;
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) {
// logger.warn(`Notification send completed with ${succeeded} success, ${failed} failure(s)`);
}
// Return true if all notificaitons succeeded
return succeeded === notifications.length;
};
handleNotifications = async (
@@ -51,12 +66,11 @@ export class NotificationsService implements INotificationsService {
const metrics = payload?.data ?? null;
if (metrics === null) return false; // No metrics, we're done
// We shoul dsend a notificaiton
// We should send a notificaiton
const shouldSend = shouldSendHardwareAlert(monitor, monitorStatusResponse);
if (shouldSend === false) return false;
console.log(JSON.stringify(monitor, null, 2));
console.log(JSON.stringify(monitorStatusResponse, null, 2));
// const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
// const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor);
// const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor);