Skip to content
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

Merged
merged 42 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
28f5d35
Add Discord webhook notification support
Skorpios604 Jan 22, 2025
172597e
Add Slack webhook integration.
Skorpios604 Jan 23, 2025
d3810db
Added telegram notifications
Skorpios604 Jan 26, 2025
d83773a
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Jan 26, 2025
84fe58b
Refactor webhook integrations functions.
Skorpios604 Jan 26, 2025
92d9573
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 2, 2025
cc48f1d
Use the NetworkService for making network requests.
Skorpios604 Feb 2, 2025
8667f74
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 3, 2025
0bb7eae
Got rid of imports.
Skorpios604 Feb 3, 2025
d3fb41a
Refactor BASE URL.
Skorpios604 Feb 3, 2025
8f94062
Store bot token and chat id in their own respective fields.
Skorpios604 Feb 3, 2025
1d9f94c
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 4, 2025
e44cbf3
Stop execution in the event of an unwanted platform.
Skorpios604 Feb 4, 2025
aa977ce
Refactor out to a template for easier message maintenance.
Skorpios604 Feb 4, 2025
964a923
Returned comments.
Skorpios604 Feb 4, 2025
e9bcf66
Resolved merge conflict in Server/index.js
Skorpios604 Feb 9, 2025
64c5aa9
Added notifcation controller and route.
Skorpios604 Feb 9, 2025
01e73fe
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 9, 2025
d8379f7
Configured a config object in the notification schema.
Skorpios604 Feb 9, 2025
f46b076
Refactored notification schema config object.
Skorpios604 Feb 9, 2025
ccd6c58
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 11, 2025
ef41712
Got rid of falsey value.
Skorpios604 Feb 11, 2025
fa50706
Simplified network response.
Skorpios604 Feb 11, 2025
67da574
Secured notifications route.
Skorpios604 Feb 11, 2025
023d943
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 13, 2025
012483d
Automated validation via joi.
Skorpios604 Feb 13, 2025
8007daa
Used response handling middleware for the response format.
Skorpios604 Feb 13, 2025
70ffe1b
Use middleware for handling errors.
Skorpios604 Feb 13, 2025
12d9f15
Got rid context bind in the route.
Skorpios604 Feb 13, 2025
07882e0
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 17, 2025
8409571
Defined new schema for the config object.
Skorpios604 Feb 17, 2025
55c1a1c
Moved validation to the controller.
Skorpios604 Feb 17, 2025
ff2b3b4
Removed timing request for the webhook.
Skorpios604 Feb 17, 2025
6152679
Update Docs.
Skorpios604 Feb 17, 2025
e4e3f9c
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 17, 2025
f22b67a
Use the localization service for user facing strings.
Skorpios604 Feb 17, 2025
d710ec0
Merge remote-tracking branch 'upstream/develop' into feat/be/webhook-…
Skorpios604 Feb 17, 2025
c4168a5
Removed null and replace with undefined.
Skorpios604 Feb 17, 2025
afc9d46
Refactored notification messsages into an array of acceptable types.
Skorpios604 Feb 17, 2025
03a938d
Check if platform type is accepted before formatting the message.
Skorpios604 Feb 17, 2025
5be17af
Used 1 validation schema for all platforms.
Skorpios604 Feb 18, 2025
4ef2b29
Used string service instead of hardcoded value.
Skorpios604 Feb 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Server/db/models/Notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const NotificationSchema = mongoose.Schema(
},
type: {
type: String,
enum: ["email", "sms"],
enum: ["email", "sms", "discord", "slack", "telegram"],
Copy link
Collaborator

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

Copy link
Member Author

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.

},
address: {
type: String,
Expand Down
89 changes: 82 additions & 7 deletions Server/service/notificationService.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const SERVICE_NAME = "NotificationService";
import axios from 'axios';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No imports in the services please, it makes testing much more difficult 👍

There shouldn't be any imports anywhere except in the root server definition really. The exception being success/error messages

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


class NotificationService {
static SERVICE_NAME = SERVICE_NAME;
Expand All @@ -16,6 +17,77 @@ class NotificationService {
this.logger = logger;
}

async sendDiscordNotification(networkResponse, webhookUrl) {
const { monitor, status } = networkResponse;
const message = {
content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`
};

try {
await axios.post(webhookUrl, message);
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Network operations should be carried out from the NetworkService class, let's keep all our network requests in one place with consistent error handling.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complete.

} catch (error) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "sendDiscordNotification",
stack: error.stack,
});
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this error meant to be swallowed and operations meant to continue in event of error?

If not it should have method specifics added to it and rethrown to be caught by middleware

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this if/else ladder meant to fall through at the end?

Should we continue execution if the platform is not one of slack, discord, or telegram? Or are we meant to return early?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, sorry, you are right. It should not continue execution. I wrote an else to return early.

}

async sendSlackNotification(networkResponse, webhookUrl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendSlackNotification and sendDiscordNotification are identical exept for the message as far as I can tell.

We can simplify and just simply have

sendWebHook({webHookUrl, msg, networkResponse})

All webhooks are going to be pretty much the same

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const { monitor, status } = networkResponse;
const message = {
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`
};

try {
await axios.post(webhookUrl, message);
return true;
} catch (error) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "sendSlackNotification",
stack: error.stack,
});
return false;
}
}

async sendTelegramNotification(networkResponse, address) {
const { monitor, status } = networkResponse;

const [botToken, chatId] = address.split('|').map(part => part?.trim());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is odd, we are using the address to store a token and and ID?

Creative solution, but I think it would be better to properly store these in their own fields rather than hack something together to make use of the address field

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


if (!botToken || !chatId) {
return false;
}

const message = {
chat_id: chatId,
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`,
};

const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base URLs should be refcatored to constant vars in one place for easy maintenance

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


try {
await axios.post(url, message, {
headers: {
'Content-Type': 'application/json',
},
});
return true;
} catch (error) {
return false;
}
}




/**
* Sends an email notification for hardware infrastructure alerts
*
Expand Down Expand Up @@ -57,19 +129,22 @@ 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
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 (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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should all be updated to use the webhook function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

// Handle other types of notifications here
}
return true;
Expand Down