-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Discord webhook notification support #1612
Changes from 7 commits
28f5d35
172597e
d3810db
d83773a
84fe58b
92d9573
cc48f1d
8667f74
0bb7eae
d3fb41a
8f94062
1d9f94c
e44cbf3
aa977ce
964a923
e9bcf66
64c5aa9
01e73fe
d8379f7
f46b076
ccd6c58
ef41712
fa50706
67da574
023d943
012483d
8007daa
70ffe1b
12d9f15
07882e0
8409571
55c1a1c
ff2b3b4
6152679
e4e3f9c
f22b67a
d710ec0
c4168a5
afc9d46
03a938d
5be17af
4ef2b29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be removed now, this was used for testing correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make sure to secure this route There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All secured. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yo, secure this endpoint! The notification routes are exposed without JWT verification, which could allow unauthorized access. - app.use("/api/v1/notifications", notificationRoutes.getRouter());
+ app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); 📝 Committable suggestion
Suggested change
|
||||||
app.use(handleErrors); | ||||||
}; | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
} | ||
}) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we timing the webook request? I don't think we're interested in how long it took to carry out the webhook request are we? We can just make the axios request directly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. No need. Got rid of it. |
||
|
||
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. | ||
* | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||||
const SERVICE_NAME = "NotificationService"; | ||||||||||||||||||||||||||||||||||||||||||
import NetworkService from "./networkService.js"; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
class NotificationService { | ||||||||||||||||||||||||||||||||||||||||||
static SERVICE_NAME = SERVICE_NAME; | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -9,13 +11,55 @@ class NotificationService { | |||||||||||||||||||||||||||||||||||||||||
* @param {Object} db - The database instance for storing notification data. | ||||||||||||||||||||||||||||||||||||||||||
* @param {Object} logger - The logger instance for logging activities. | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be sure to update the JSdoc since this now takes a |
||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of first trying to build a message and then checking if the message is undefined or not, let's not even try to build a message if the webhook type is not supported. Just check if the type is in the acceptable type array described above, if not return early. We should always try to return as early as possible to do as little work as necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. |
||||||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||||||
ajhollid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||
* Sends an email notification for hardware infrastructure alerts | ||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -57,21 +101,20 @@ class NotificationService { | |||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
async handleStatusNotifications(networkResponse) { | ||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||
//If status hasn't changed, we're done | ||||||||||||||||||||||||||||||||||||||||||
if (networkResponse.statusChanged === false) return false; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// if prevStatus is undefined, monitor is resuming, we're done | ||||||||||||||||||||||||||||||||||||||||||
ajhollid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
if (networkResponse.prevStatus === undefined) return false; | ||||||||||||||||||||||||||||||||||||||||||
const notifications = await this.db.getNotificationsByMonitorId( | ||||||||||||||||||||||||||||||||||||||||||
networkResponse.monitorId | ||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
const notifications = await this.db.getNotificationsByMonitorId(networkResponse.monitorId); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
for (const notification of notifications) { | ||||||||||||||||||||||||||||||||||||||||||
if (notification.type === "email") { | ||||||||||||||||||||||||||||||||||||||||||
this.sendEmail(networkResponse, notification.address); | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} else if (["discord", "slack", "telegram"].includes(notification.type)) { | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be simplified to just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good suggestion. Thank you. All done. |
||||||||||||||||||||||||||||||||||||||||||
this.sendWebhookNotification(networkResponse, notification.address, notification.type); | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
// Handle other types of notifications here | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yo, there's a syntax error in this loop! The for loop is missing a closing brace. Here's the fix: for (const notification of notifications) {
if (notification.type === "email") {
this.sendEmail(networkResponse, notification.address);
} else if (["discord", "slack", "telegram"].includes(notification.type)) {
this.sendWebhookNotification(networkResponse, notification.address, notification.type);
}
- // Handle other types of notifications here
}
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
this.logger.warn({ | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need all these types anymore do we? They are all of type "webhook" now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup! You are right. All done.