diff --git a/Server/index.js b/Server/index.js index 7a4129bc9..a826ce64b 100644 --- a/Server/index.js +++ b/Server/index.js @@ -38,6 +38,10 @@ import QueueController from "./controllers/queueController.js"; import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js"; import DistributedUptimeController from "./controllers/distributedUptimeController.js"; +import NotificationRoutes from "./routes/notificationRoute.js"; // Add this line + +import NotificationController from "./controllers/notificationController.js"; + //JobQueue service and dependencies import JobQueue from "./service/jobQueue.js"; import { Queue, Worker } from "bullmq"; @@ -174,7 +178,7 @@ const startApp = async () => { ); const networkService = new NetworkService(axios, ping, logger, http, Docker, net); const statusService = new StatusService(db, logger); - const notificationService = new NotificationService(emailService, db, logger); + const notificationService = new NotificationService(emailService, db, logger, networkService); const jobQueue = new JobQueue( db, @@ -244,6 +248,10 @@ const startApp = async () => { ServiceRegistry.get(MongoDB.SERVICE_NAME) ); + const notificationController = new NotificationController( + ServiceRegistry.get(NotificationService.SERVICE_NAME) + ); + const distributedUptimeController = new DistributedUptimeController(); //Create routes @@ -260,6 +268,9 @@ const startApp = async () => { const distributedUptimeRoutes = new DistributedUptimeRoutes( distributedUptimeController ); + + const notificationRoutes = new NotificationRoutes(notificationController); + // Init job queue await jobQueue.initJobQueue(); // Middleware @@ -284,6 +295,7 @@ const startApp = async () => { app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter()); app.use("/api/v1/status-page", statusPageRoutes.getRouter()); + app.use("/api/v1/notifications", notificationRoutes.getRouter()); // Add this line app.use(handleErrors); }; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 54edf39f9..604e2bbe6 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -336,6 +336,52 @@ class NetworkService { throw err; } + async requestWebhook(platform, url, message) { + try { + const { response, responseTime, error } = await this.timeRequest(() => + this.axios.post(url, message, { + headers: { + 'Content-Type': 'application/json' + } + }) + ); + + const webhookResponse = { + type: 'webhook', + responseTime, + payload: response?.data + }; + + if (error) { + webhookResponse.status = false; + webhookResponse.code = error.response?.status || this.NETWORK_ERROR; + webhookResponse.message = `Failed to send ${platform} notification`; + this.logger.warn({ + message: error.message, + service: this.SERVICE_NAME, + method: 'requestWebhook', + url, + platform, + error: error.message, + statusCode: error.response?.status, + responseData: error.response?.data, + requestPayload: message + }); + return webhookResponse; + } + + webhookResponse.status = true; + webhookResponse.code = response.status; + webhookResponse.message = `Successfully sent ${platform} notification`; + return webhookResponse; + } catch (error) { + error.service = this.SERVICE_NAME; + error.method = 'requestWebhook'; + throw error; + } + } + + /** * Gets the status of a job based on its type and returns the appropriate response. * diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index a54afd506..bd65daf46 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -1,5 +1,6 @@ const SERVICE_NAME = "NotificationService"; -import axios from 'axios'; +import NetworkService from "./networkService.js"; + class NotificationService { static SERVICE_NAME = SERVICE_NAME; @@ -10,50 +11,53 @@ class NotificationService { * @param {Object} db - The database instance for storing notification data. * @param {Object} logger - The logger instance for logging activities. */ - constructor(emailService, db, logger) { + constructor(emailService, db, logger, networkService) { this.SERVICE_NAME = SERVICE_NAME; this.emailService = emailService; this.db = db; this.logger = logger; + this.networkService = networkService; } async sendWebhookNotification(networkResponse, address, platform) { - const { monitor, status } = networkResponse; - let message; - let url = address; - - if (platform === 'slack') { - message = { text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; - } else if (platform === 'discord') { - message = { content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; - } else if (platform === 'telegram') { - const [botToken, chatId] = address.split('|').map(part => part?.trim()); - if (!botToken || !chatId) { - console.error('Invalid Telegram address format'); - return false; - } - message = { - chat_id: chatId, - text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` - }; - url = `https://api.telegram.org/bot${botToken}/sendMessage`; - } - - try { - await axios.post(url, message, { - headers: { - 'Content-Type': 'application/json' - } - }); - return true; - } catch (error) { - - console.error(`Error sending ${platform} notification:`, error.toJSON()); - console.error(`URL: ${url}`); - console.error(`Message:`, message); - return false; - } - } + const { monitor, status } = networkResponse; + let message; + let url = address; + + if (platform === 'slack') { + message = { text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; + } else if (platform === 'discord') { + message = { content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; + } else if (platform === 'telegram') { + const [botToken, chatId] = address.split('|').map(part => part?.trim()); + if (!botToken || !chatId) { + return false; + } + message = { + chat_id: chatId, + text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + }; + url = `https://api.telegram.org/bot${botToken}/sendMessage`; + } + + try { + const response = await this.networkService.requestWebhook(platform, url, message); + return response.status; + } catch (error) { + this.logger.error({ + message: `Error sending ${platform} notification`, + service: this.SERVICE_NAME, + method: 'sendWebhookNotification', + error: error.message, + stack: error.stack, + url, + platform, + requestPayload: message + }); + return false; + } + } + /** @@ -105,16 +109,12 @@ class NotificationService { for (const notification of notifications) { if (notification.type === "email") { this.sendEmail(networkResponse, notification.address); - } else if (notification.type === "discord") { - this.sendDiscordNotification(networkResponse, notification.address); - } else if (notification.type === "slack") { - this.sendSlackNotification(networkResponse, notification.address); - } else if (notification.type === "telegram") { - this.sendTelegramNotification(networkResponse, notification.address); - } + } else if (["discord", "slack", "telegram"].includes(notification.type)) { + this.sendWebhookNotification(networkResponse, notification.address, notification.type); // Handle other types of notifications here } + } return true; } catch (error) { this.logger.warn({