-
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
Conversation
WalkthroughThe pull request enhances the notification system by modifying the Changes
Suggested reviewers
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
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.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
Server/service/notificationService.js (1)
Line range hint
91-102
: There's vomit on his sweater - we forgot to handle Discord notifications! 🍝The handleStatusNotifications method doesn't handle the new "discord" notification type.
Add Discord support to the notification handler:
for (const notification of notifications) { if (notification.type === "email") { this.sendEmail(networkResponse, notification.address); } - // Handle other types of notifications here + else if (notification.type === "discord") { + await this.sendDiscordNotification(networkResponse, notification.webhookUrl); + } }
🧹 Nitpick comments (1)
Server/service/notificationService.js (1)
20-38
: His palms are sweaty - we need some tests! 🍝The new Discord notification functionality lacks test coverage.
Would you like me to generate unit tests for the Discord notification functionality? The tests would cover:
- Webhook URL validation
- Message formatting
- Rate limiting handling
- Error scenarios
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Server/db/models/Notification.js
(1 hunks)Server/service/notificationService.js
(2 hunks)
Server/db/models/Notification.js
Outdated
@@ -8,7 +8,7 @@ const NotificationSchema = mongoose.Schema( | |||
}, | |||
type: { | |||
type: String, | |||
enum: ["email", "sms"], | |||
enum: ["email", "sms", "discord"], |
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.
Yo dawg, we need a field for that webhook URL! 🍝
While adding "discord" to the enum is a good start, we're missing a dedicated field to store the Discord webhook URL. The existing address
field could be misused for this, but that's not clean architecture.
Add a new field to properly store webhook URLs:
type: {
type: String,
enum: ["email", "sms", "discord"],
},
+webhookUrl: {
+ type: String,
+ validate: {
+ validator: function(v) {
+ return this.type !== "discord" ||
+ /^https:\/\/discord\.com\/api\/webhooks\/[0-9]+\/.*$/.test(v);
+ },
+ message: "Invalid Discord webhook URL format"
+ }
+},
Committable suggestion skipped: line range outside the PR's diff.
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.
Auto Pull Request Review from LlamaPReview
1. Overview
1.1 PR Summary
- Business value and requirements alignment: This PR introduces Discord webhook notification support, enhancing the existing monitoring system by adding a new notification channel. This aligns with the business requirement of expanding user notification options beyond email and SMS, thereby improving user experience and engagement.
- Key components modified: The PR modifies the
Notification
model to include Discord as a notification type and adds a new functionsendDiscordNotification
in thenotificationService
to handle Discord notifications. - Impact assessment: The addition of Discord notifications introduces a new external dependency (Discord API) and requires handling asynchronous operations and potential network failures. This impacts the system's architecture by adding a new notification channel and integrating with an external service.
- System dependencies and integration impacts: The new feature depends on the Discord API for sending notifications. Proper error handling and fallback mechanisms are crucial to maintain system stability. The integration also introduces security considerations, such as securely managing webhook URLs.
1.2 Architecture Changes
- System design modifications: The system design is modified to include Discord as a notification channel. This involves changes to the
Notification
model and the addition of a new function in thenotificationService
. - Component interactions: The
notificationService
now interacts with the Discord API to send notifications. This introduces a new external dependency and requires handling asynchronous operations. - Integration points: The integration point is the
sendDiscordNotification
function, which makes outbound HTTP requests to the Discord API.
2. Detailed Technical Analysis
2.1 Code Logic Deep-Dive
Core Logic Changes
- Server/service/notificationService.js - sendDiscordNotification
- Submitted PR Code:
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;
} catch (error) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "sendDiscordNotification",
stack: error.stack,
});
return false;
}
}
- Analysis:
- The current error handling in
sendDiscordNotification
is basic. It catches any error during theaxios.post
call, logs it, and returnsfalse
. - It lacks specific error handling based on the type of error. For example, Discord API might return different status codes for various issues like invalid webhook URL (400), rate limiting (429), or server errors (5xx).
- There is no retry mechanism for transient network errors or temporary Discord API issues. Simply logging and returning
false
might lead to missed notifications even if the issue is temporary. - The error log message includes
error.message
anderror.stack
, which is good for debugging, but it could be enhanced with more context, such as thewebhookUrl
being used (obfuscated for security in production logs, but helpful in development/debugging) and themonitor.name
to easily identify the source of the notification failure.
- The current error handling in
- LlamaPReview Suggested Improvements:
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;
} catch (error) {
this.logger.error({
message: `Discord notification failed for monitor: ${monitor.name}, webhookUrl: ${webhookUrl.substring(0, 20)}... Error: ${error.message}`, // Improved logging with monitor name and partial webhook URL
service: this.SERVICE_NAME,
method: "sendDiscordNotification",
stack: error.stack,
});
// Consider adding retry logic here for transient errors, potentially with exponential backoff.
// Example (basic retry - for production, use a more robust library/strategy):
// if (error.response && error.response.status >= 500) {
// await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second before retry
// try {
// await axios.post(webhookUrl, message);
// return true; // Retry success
// } catch (retryError) {
// this.logger.error({ ...retryErrorLog }); // Log retry error
// return false; // Retry failed
// }
// }
return false;
}
}
- Improvement rationale:
- Technical benefits:
- Improved error logging: Provides more context for debugging notification failures, including the monitor name and a truncated webhook URL for identification without exposing the full secret.
- Foundation for retry logic: The suggested code includes comments and a basic example of where retry logic could be implemented. This addresses the potential for transient network errors and improves the reliability of notifications. A more robust retry mechanism (using libraries like
axios-retry
or implementing exponential backoff) would be beneficial in a production environment.
- Business value:
- Increased notification reliability: Retry logic, even basic, can significantly reduce missed notifications due to temporary issues, leading to more reliable alerting for users.
- Risk assessment:
- Low risk: The logging improvement is a low-risk change. Adding retry logic introduces slightly more complexity but can be implemented incrementally and tested thoroughly. Not implementing retry logic carries the risk of unreliable notifications.
- Technical benefits:
Core Logic Changes
- Server/service/notificationService.js - sendDiscordNotification
- Submitted PR Code:
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;
} catch (error) {
this.logger.error({
message: error.message,
service: this.SERVICE_NAME,
method: "sendDiscordNotification",
stack: error.stack,
});
return false;
}
}
- Analysis:
- The
sendDiscordNotification
function directly takeswebhookUrl
as a parameter. This implies that the webhook URL is being passed to this function from elsewhere in the application. - The PR does not show where this
webhookUrl
originates from, how it is stored, or how it is managed. This is a significant security and operational gap. - Security Implications: If webhook URLs are not handled securely, they could be exposed, leading to unauthorized access or misuse of the Discord webhook. Hardcoding webhook URLs is a major security vulnerability and scalability issue. Storing them in plain text in a database or configuration file without proper access control is also risky.
- Missing Input Validation and Sanitization: The code does not show any validation or sanitization of the
webhookUrl
before using it in theaxios.post
request. It's crucial to validate that the URL is a valid webhook URL format and potentially sanitize it to prevent injection attacks (though less likely in this context, validation is still important).
- The
- LlamaPReview Suggested Improvements: [Improvement focuses on highlighting the missing aspects, as the PR doesn't include the webhook URL management part]
// ... (rest of the code remains the same as the submitted PR code for this function)
// --- CRITICAL SECURITY & OPERATIONAL CONSIDERATIONS (NOT INCLUDED IN PR CODE SNIPPET) ---
// 1. Webhook URL Source and Storage:
// - Where does the 'webhookUrl' parameter come from?
// - How are webhook URLs stored? (e.g., database, configuration file, secrets manager)
// - Are webhook URLs encrypted at rest?
// - Is access to webhook URLs controlled and restricted?
// 2. Webhook URL Management:
// - Is there a UI or API for users to add/manage their Discord webhook URLs?
// - How are webhook URLs associated with monitors or notification configurations?
// 3. Input Validation and Sanitization:
// - Before calling sendDiscordNotification, the application MUST validate and sanitize the webhookUrl.
// - Validate URL format to ensure it's a valid URL.
// - Sanitize to prevent any potential injection risks (though less likely with webhook URLs, defense in depth is good practice).
// 4. Security Best Practices:
// - Treat webhook URLs as sensitive secrets.
// - Avoid logging full webhook URLs in production logs.
// - Consider using environment variables or a secrets management system to store webhook URLs securely.
async sendDiscordNotification(networkResponse, webhookUrl) { // webhookUrl is passed in - its origin and security are critical missing pieces
// ... (function code as in submitted PR) ...
}
- Improvement rationale:
- Technical benefits:
- Highlights critical security gaps: Explicitly points out the missing aspects of webhook URL management, storage, and security, which are essential for a production-ready feature.
- Emphasizes security best practices: Recommends treating webhook URLs as secrets, secure storage, and input validation, aligning with industry security standards.
- Business value:
- Prevents security vulnerabilities: Addressing webhook URL security is crucial to prevent potential misuse and data breaches, protecting user data and the application's integrity.
- Ensures operational robustness: Proper management and storage of webhook URLs are necessary for the long-term maintainability and scalability of the Discord notification feature.
- Risk assessment:
- High risk if not addressed: Failure to secure webhook URLs poses a significant security risk. The suggested improvements are crucial to mitigate this high risk. The PR, in its current form, is incomplete from a security perspective regarding secret management.
- Technical benefits:
2.2 Implementation Quality
- Code organization and structure: The code is well-organized, with the new
sendDiscordNotification
function clearly defined within thenotificationService
. The addition ofaxios
for HTTP requests is appropriate for the task. - Design patterns usage: The use of a service class (
NotificationService
) to handle notifications is a good design pattern, promoting separation of concerns and maintainability. - Error handling approach: The current error handling is basic but can be improved with more specific error handling and retry logic, as suggested in the deep-dive analysis.
- Resource management: The code efficiently manages resources by using asynchronous operations with
axios
for HTTP requests.
3. Critical Findings
3.1 Potential Issues
-
🔴 Critical Issues
- Webhook URL Management: The PR does not address how webhook URLs are managed, stored, or validated. This poses a significant security risk, as improper handling of webhook URLs can lead to unauthorized access or misuse.
- Impact: Potential security vulnerabilities and operational issues.
- Recommendation: Implement secure storage, management, and validation of webhook URLs. Treat them as sensitive secrets and follow security best practices.
- Webhook URL Management: The PR does not address how webhook URLs are managed, stored, or validated. This poses a significant security risk, as improper handling of webhook URLs can lead to unauthorized access or misuse.
-
🟡 Warnings
- Basic Error Handling: The current error handling in
sendDiscordNotification
is basic and lacks specific handling for different types of errors.- Potential risks: Missed notifications due to temporary issues, lack of context in error logs for debugging.
- Suggested improvements: Enhance error logging with more context, such as the monitor name and a truncated webhook URL. Consider implementing retry logic for transient errors.
- Basic Error Handling: The current error handling in
3.2 Code Quality Concerns
- Maintainability aspects: The code is maintainable, but the lack of specific error handling and retry logic could lead to maintenance challenges in the future.
- Readability issues: The code is generally readable, but adding more context to error logs and implementing retry logic would improve readability and debuggability.
- Performance bottlenecks: The current implementation does not indicate any significant performance bottlenecks. However, adding retry logic should consider performance implications, such as exponential backoff to avoid overwhelming the Discord API.
4. Security Assessment
- Authentication/Authorization impacts: The PR does not introduce changes to authentication or authorization mechanisms. However, ensuring that only authorized users can manage webhook URLs is crucial.
- Data handling concerns: Webhook URLs must be handled securely to prevent unauthorized access or misuse.
- Input validation: The PR lacks input validation for webhook URLs. It is essential to validate and sanitize webhook URLs before use.
- Security best practices: Treat webhook URLs as sensitive secrets. Avoid logging full webhook URLs in production logs. Consider using environment variables or a secrets management system to store webhook URLs securely.
- Potential security risks: Improper handling of webhook URLs poses a significant security risk.
- Mitigation strategies: Implement secure storage, management, and validation of webhook URLs. Follow security best practices for handling sensitive secrets.
- Security testing requirements: Conduct security testing to ensure that webhook URLs are handled securely and that the Discord integration does not introduce vulnerabilities.
5. Testing Strategy
5.1 Test Coverage
- Unit test analysis: Ensure that the
sendDiscordNotification
function is covered by unit tests, including tests for successful notifications and various error scenarios. - Integration test requirements: Implement integration tests to confirm end-to-end Discord notification delivery. Include tests that simulate Discord API failures and network outages.
- Edge cases coverage: Cover edge cases such as invalid webhook URLs, rate limiting by the Discord API, and temporary network issues.
5.2 Test Recommendations
Suggested Test Cases
// Unit test for successful Discord notification
it('should send Discord notification successfully', async () => {
const networkResponse = { monitor: { name: 'Test Monitor', url: 'http://example.com' }, status: true };
const webhookUrl = 'https://discord.com/api/webhooks/...';
const mockAxios = jest.spyOn(axios, 'post').mockResolvedValueOnce({});
const result = await notificationService.sendDiscordNotification(networkResponse, webhookUrl);
expect(result).toBe(true);
expect(mockAxios).toHaveBeenCalledWith(webhookUrl, {
content: `Monitor Test Monitor is up. URL: http://example.com`
});
});
// Unit test for Discord notification failure
it('should handle Discord notification failure and log error', async () => {
const networkResponse = { monitor: { name: 'Test Monitor', url: 'http://example.com' }, status: false };
const webhookUrl = 'https://discord.com/api/webhooks/...';
const mockAxios = jest.spyOn(axios, 'post').mockRejectedValueOnce(new Error('Test error'));
const mockLogger = jest.spyOn(notificationService.logger, 'error').mockImplementationOnce(() => {});
const result = await notificationService.sendDiscordNotification(networkResponse, webhookUrl);
expect(result).toBe(false);
expect(mockAxios).toHaveBeenCalledWith(webhookUrl, {
content: `Monitor Test Monitor is down. URL: http://example.com`
});
expect(mockLogger).toHaveBeenCalledWith(expect.objectContaining({
message: expect.stringContaining('Discord notification failed for monitor: Test Monitor, webhookUrl: https://discord.com/api/webhooks/... Error: Test error')
}));
});
- Coverage improvements: Ensure that tests cover all possible error scenarios, including different HTTP status codes returned by the Discord API.
- Performance testing needs: Conduct performance testing to ensure that the Discord integration can handle the expected load and that retry logic (if implemented) does not overwhelm the Discord API.
6. Documentation & Maintenance
- Documentation updates needed: Update the API documentation to include the new Discord notification feature. Provide guidelines on how to securely manage and store webhook URLs.
- Long-term maintenance considerations: Ensure that webhook URLs are managed securely and that the Discord integration is monitored for any issues. Implement robust error handling and retry logic to improve the reliability of notifications.
- Technical debt and monitoring requirements: Monitor the Discord integration for any issues and address technical debt related to error handling and retry logic.
7. Deployment & Operations
- Deployment impact and strategy: The deployment of this feature introduces a new external dependency (Discord API). Ensure that the deployment process includes testing the Discord integration in a staging environment before deploying to production.
- Key operational considerations: Monitor the Discord integration for any issues and ensure that webhook URLs are managed securely. Implement robust error handling and retry logic to improve the reliability of notifications.
8. Summary & Recommendations
8.1 Key Action Items
- Implement secure storage, management, and validation of webhook URLs.
- Enhance error logging with more context, such as the monitor name and a truncated webhook URL.
- Consider implementing retry logic for transient errors to improve the reliability of notifications.
- Conduct security testing to ensure that webhook URLs are handled securely and that the Discord integration does not introduce vulnerabilities.
8.2 Future Considerations
- Technical evolution path: Continuously improve the error handling and retry logic for the Discord integration. Explore using more robust libraries or strategies for retry logic.
- Business capability evolution: Expand the notification capabilities to include more channels and features based on user feedback and business requirements.
- System integration impacts: Ensure that the Discord integration is monitored for any issues and that it can handle the expected load. Address any technical debt related to error handling and retry logic.
💡 Help Shape LlamaPReview
How's this review format working for you? Vote in our Github Discussion Polls to help us improve your review experience!
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.
Actionable comments posted: 1
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Server/db/models/Notification.js
(1 hunks)Server/service/notificationService.js
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Server/db/models/Notification.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
Server/service/notificationService.js (3)
2-2
: Yo dawg, we need to lock down that axios version! 🍝The axios import is using a loose version in package.json. This could lead to unexpected breaks when dependencies update.
20-38
: Knees weak, arms are heavy - this Discord notification needs more already! 🍝The current implementation needs improvement in several areas:
- No webhook URL validation
- Basic message format (Discord supports rich embeds)
- No rate limiting consideration
- No retry mechanism for failed requests
40-58
: 🛠️ Refactor suggestionMom's spaghetti alert - this Slack notification is too basic! 🍝
The Slack implementation needs similar improvements:
async sendSlackNotification(networkResponse, webhookUrl) { + if (!webhookUrl?.startsWith('https://hooks.slack.com/services/')) { + throw new Error('Invalid Slack webhook URL'); + } + const { monitor, status } = networkResponse; const message = { - text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `*Monitor Status Change*\n${status ? "🟢" : "🔴"} ${monitor.name}` + } + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Status:*\n${status ? "Operational" : "Down"}` + }, + { + type: "mrkdwn", + text: `*URL:*\n${monitor.url}` + } + ] + } + ] }; try { - await axios.post(webhookUrl, message); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + await axios.post(webhookUrl, message, { + signal: controller.signal, + headers: { 'Content-Type': 'application/json' } + }); + + clearTimeout(timeoutId); return true; } catch (error) { + if (error.response?.status === 429) { + const retryAfter = error.response.headers['retry-after'] || 5; + await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); + return this.sendSlackNotification(networkResponse, webhookUrl); + } + this.logger.error({ message: error.message, service: this.SERVICE_NAME, method: "sendSlackNotification", - stack: error.stack, + stack: error.stack, + status: error.response?.status, + data: error.response?.data }); return false; } }Likely invalid or redundant comment.
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.
Great start!
We should use the NetworkService
class to make network requests as it alreaedy is provisioned with an instance of Axios
and all our network requests should be in the same place.
The two webhook methods are nearly identical and can be refactored into one reusable method.
I'm not sure if the errors here are meant to be swallowed or not, my guess is probably yes? Make sure you keep the flow of the application in mind, IE the the operation of making a check, parsing its data, and inserting it into the database should not be interrupted by the failure of a notification to send.
|
||
try { | ||
await axios.post(webhookUrl, message); | ||
return true; |
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.
Network operations should be carried out from the NetworkService
class, let's keep all our network requests in one place with consistent error handling.
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.
Complete.
method: "sendDiscordNotification", | ||
stack: error.stack, | ||
}); | ||
return false; |
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.
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
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.
Yes!
@@ -1,4 +1,5 @@ | |||
const SERVICE_NAME = "NotificationService"; | |||
import axios from 'axios'; |
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.
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
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.
Done.
} | ||
} | ||
|
||
async sendSlackNotification(networkResponse, webhookUrl) { |
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.
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
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.
Done.
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.
Actionable comments posted: 2
♻️ Duplicate comments (1)
Server/service/notificationService.js (1)
40-58
: 🛠️ Refactor suggestionDuplicate code making my knees weak!
This method is practically identical to sendDiscordNotification. Let's consolidate these into a single webhook method.
-async sendSlackNotification(networkResponse, webhookUrl) { - // ... duplicate code ... -} +async sendNotification(type, networkResponse, webhookUrl) { + const { monitor, status } = networkResponse; + const message = { + [type === 'slack' ? 'text' : 'content']: + `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + }; + return this.networkService.sendWebhook(webhookUrl, message); +}
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Server/db/models/Notification.js
(1 hunks)Server/service/notificationService.js
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Server/db/models/Notification.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
Server/service/notificationService.js (2)
2-2
: Yo dawg, we gotta move that import outta here!Previous feedback already mentioned this - services shouldn't have direct imports to make testing easier. Pass axios as a dependency through the constructor instead.
-import axios from 'axios'; class NotificationService { constructor(emailService, db, logger) { this.SERVICE_NAME = SERVICE_NAME; this.emailService = emailService; this.db = db; this.logger = logger; + this.httpClient = null; }
20-38
:⚠️ Potential issueMom's spaghetti moment - we got some cleanup to do here!
Several issues need addressing:
- Network operations should live in NetworkService
- Silent error handling needs improvement - either propagate or handle explicitly
- Webhook logic is duplicated across notification methods
Consider consolidating webhook logic into a single method in NetworkService:
+async sendWebhook(url, message, options = {}) { + try { + const response = await this.httpClient.post(url, message, options); + return { success: true, data: response.data }; + } catch (error) { + throw new NotificationError(`Failed to send webhook: ${error.message}`, { + cause: error, + service: this.SERVICE_NAME, + method: 'sendWebhook' + }); + } +}Likely invalid or redundant comment.
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.
Good progress, some minor concerns but otherwise looks to be on the right track 👍
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`, | ||
}; | ||
|
||
const url = `https://api.telegram.org/bot${botToken}/sendMessage`; |
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.
Base URLs should be refcatored to constant vars in one place for easy maintenance
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.
Done.
async sendTelegramNotification(networkResponse, address) { | ||
const { monitor, status } = networkResponse; | ||
|
||
const [botToken, chatId] = address.split('|').map(part => part?.trim()); |
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.
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
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.
Done.
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.
Actionable comments posted: 3
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Server/service/notificationService.js
(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
Server/service/notificationService.js (2)
2-2
: Yo dawg, we gotta move these network ops to NetworkService! 🍝Previous feedback already highlighted this - services shouldn't have direct imports. Let's keep all network operations in NetworkService for consistent error handling and easier testing.
108-113
:⚠️ Potential issueThere's vomit on the sweater already - these methods don't exist! 🍝
The code references
sendDiscordNotification
,sendSlackNotification
, andsendTelegramNotification
, but these methods aren't implemented. Should be using the newsendWebhookNotification
method instead.Here's the fix:
} else if (notification.type === "discord") { - this.sendDiscordNotification(networkResponse, notification.address); + await this.sendWebhookNotification(networkResponse, notification.address, 'discord'); } else if (notification.type === "slack") { - this.sendSlackNotification(networkResponse, notification.address); + await this.sendWebhookNotification(networkResponse, notification.address, 'slack'); } else if (notification.type === "telegram") { - this.sendTelegramNotification(networkResponse, notification.address); + await this.sendWebhookNotification(networkResponse, notification.address, 'telegram'); }Likely invalid or redundant comment.
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.
Some tidying up to do and some flow that I'm not sure of.
Looks to be still on the right track though!
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` | ||
}; | ||
url = `https://api.telegram.org/bot${botToken}/sendMessage`; | ||
} |
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.
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?
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.
No, sorry, you are right. It should not continue execution. I wrote an else to return early.
} 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()); |
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.
As mentioned in previous review this should be in its own field rather than shoehorned into the address field
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.
Complete.
chat_id: chatId, | ||
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` | ||
}; | ||
url = `https://api.telegram.org/bot${botToken}/sendMessage`; |
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.
This will be unnecessary once the token and chat ID are moved to their own fields
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.
Bot token and chat ID have been moved to their own separate fields.
let url = address; | ||
|
||
if (platform === 'slack') { | ||
message = { text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; |
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.
These can be refactored out to a template, it will clean the function up and make maintaining the messages easeir.
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.
Refactored out to a template for easier message maintenance.
|
||
console.error(`Error sending ${platform} notification:`, error.toJSON()); | ||
console.error(`URL: ${url}`); | ||
console.error(`Message:`, message); |
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.
Errors should be logged using the Logger
and standard format
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.
All done.
} else if (notification.type === "slack") { | ||
this.sendSlackNotification(networkResponse, notification.address); | ||
} else if (notification.type === "telegram") { | ||
this.sendTelegramNotification(networkResponse, notification.address); |
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.
these should all be updated to use the webhook function?
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.
Done.
} | ||
|
||
try { | ||
await axios.post(url, message, { |
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.
This still needs to be moved to the NetworkService
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.
Done.
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.
Actionable comments posted: 1
♻️ Duplicate comments (1)
Server/service/notificationService.js (1)
22-59
: 🛠️ Refactor suggestionRefactor webhook message construction and improve security.
Several improvements needed:
- Token and chatId should be stored in dedicated fields rather than concatenated in the address field
- Message templates can be extracted to reduce duplication
- Consider using environment variables for API base URLs
Apply this diff to extract message templates:
+const MESSAGE_TEMPLATES = { + slack: (monitor, status) => ({ + text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + }), + discord: (monitor, status) => ({ + content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + }), + telegram: (monitor, status) => ({ + text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` + }) +}; 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}` }; + message = MESSAGE_TEMPLATES.slack(monitor, status); } else if (platform === 'discord') { - message = { content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }; + message = MESSAGE_TEMPLATES.discord(monitor, status); } 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}` + ...MESSAGE_TEMPLATES.telegram(monitor, status) }; url = `https://api.telegram.org/bot${botToken}/sendMessage`; }
🧹 Nitpick comments (2)
Server/service/notificationService.js (1)
102-127
: Consider using early return pattern for better readability.The function structure could be improved by returning early when notifications aren't needed.
async handleStatusNotifications(networkResponse) { try { - if (networkResponse.statusChanged === false) return false; - if (networkResponse.prevStatus === undefined) return false; + if (!networkResponse.statusChanged || networkResponse.prevStatus === undefined) { + return false; + } const notifications = await this.db.getNotificationsByMonitorId(networkResponse.monitorId);Server/service/networkService.js (1)
339-382
: Consider adding rate limiting for webhook requests.While the implementation is solid, adding rate limiting would prevent potential abuse and align with platform-specific rate limits.
+const RATE_LIMITS = { + discord: { requests: 5, period: 5000 }, // 5 requests per 5 seconds + slack: { requests: 1, period: 1000 }, // 1 request per second + telegram: { requests: 30, period: 1000 } // 30 requests per second +}; async requestWebhook(platform, url, message) { try { + const limit = RATE_LIMITS[platform]; + if (limit) { + await this.rateLimiter.checkLimit(platform, limit.requests, limit.period); + } + const { response, responseTime, error } = await this.timeRequest(() =>
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Server/index.js
(5 hunks)Server/service/networkService.js
(1 hunks)Server/service/notificationService.js
(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
Server/service/notificationService.js (2)
14-20
: LGTM! Constructor changes align with NetworkService integration.The addition of networkService parameter follows the feedback about centralizing network operations.
43-58
: LGTM! Error handling looks solid.The error handling follows the established pattern:
- Detailed error logging with context
- Graceful error recovery
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.
Actionable comments posted: 0
🧹 Nitpick comments (1)
Server/service/notificationService.js (1)
110-111
: Let's keep it organized like mom's spaghetti! 🍝Extract the platform types to constants at the top of the file:
+ const NOTIFICATION_PLATFORMS = { + DISCORD: 'discord', + SLACK: 'slack', + TELEGRAM: 'telegram', + EMAIL: 'email' + }; + + const WEBHOOK_PLATFORMS = [ + NOTIFICATION_PLATFORMS.DISCORD, + NOTIFICATION_PLATFORMS.SLACK, + NOTIFICATION_PLATFORMS.TELEGRAM + ]; - } else if (["discord", "slack", "telegram"].includes(notification.type)) { + } else if (WEBHOOK_PLATFORMS.includes(notification.type)) {
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Server/service/notificationService.js
(2 hunks)
🔇 Additional comments (2)
Server/service/notificationService.js (2)
12-18
: Yo, these constructor changes are straight fire! 🔥The addition of networkService parameter aligns perfectly with the previous feedback about centralizing network operations.
20-57
: 🛠️ Refactor suggestionYo dawg, let's make this webhook code even more fire! 🎵
A few suggestions to level up this implementation:
- The message templates for each platform are nearly identical. Consider extracting them to a template:
+ const createMessage = (monitor, status, platform) => { + const text = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`; + switch(platform) { + case 'slack': return { text }; + case 'discord': return { content: text }; + case 'telegram': return { chat_id: chatId, text }; + default: throw new Error(`Unsupported platform: ${platform}`); + } + };
- The Telegram address parsing is a bit sketchy:
- const [botToken, chatId] = address.split('|').map(part => part?.trim()); + const TELEGRAM_SEPARATOR = '|'; + const [botToken, chatId] = address.split(TELEGRAM_SEPARATOR).map(part => part?.trim()); + if (!botToken || !chatId) { + this.logger.warn({ + message: 'Invalid Telegram address format', + service: this.SERVICE_NAME, + method: 'sendWebhookNotification', + address + }); + return false; + }
- Consider adding platform-specific URL validation:
✅ Verification successful
Heads up: Notification Service Webhook Code Validated
The regex search confirms that the Telegram URL is hardcoded as
https://api.telegram.org/bot${botToken}/sendMessage
, which supports the review comment’s suggestion for platform-specific URL handling. Additionally, the review suggestions for extracting message templates and improving Telegram address parsing remain applicable, as the code still embeds these implementations directly.
- The hardcoded Telegram URL in
Server/service/notificationService.js
is verified.- There’s an opportunity to add a constant separator for Telegram address parsing and log a warning on invalid formats.
- Consider refactoring message construction into a shared template for all platforms.
His palms are sweaty, knees weak, arms are heavy—let’s polish this webhook code to make it even more robust!
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for hardcoded URLs in the codebase rg -i "api\.telegram\.org|api\.slack\.com|discord\.com"Length of output: 250
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.
Actionable comments posted: 0
♻️ Duplicate comments (2)
Server/service/notificationService.js (2)
2-2
: 🛠️ Refactor suggestionMove base URL to central configuration.
Base URLs should be maintained in a central configuration file for easier maintenance and consistency across the application.
Consider moving this constant to a central configuration file (e.g.,
config/urls.js
):-const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot";
31-34
: 🛠️ Refactor suggestionRefactor Telegram credentials storage.
Using address field to store both token and chat ID is a hacky solution.
Consider adding proper fields in the notification schema:
-const [botToken, chatId] = address.split('|').map(part => part?.trim()); -if (!botToken || !chatId) { - return false; -}
🧹 Nitpick comments (1)
Server/service/notificationService.js (1)
13-18
: Add JSDoc for networkService parameter.The constructor's documentation is missing the networkService parameter.
/** * Creates an instance of NotificationService. * * @param {Object} emailService - The email service used for sending notifications. * @param {Object} db - The database instance for storing notification data. * @param {Object} logger - The logger instance for logging activities. + * @param {Object} networkService - The network service for making HTTP requests. */
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Server/service/notificationService.js
(3 hunks)
🔇 Additional comments (3)
Server/service/notificationService.js (3)
26-40
: Extract message templates to a separate configuration.Message templates should be maintained separately for easier maintenance and localization support.
Consider creating a message template configuration:
const notificationTemplates = { slack: (monitor, status) => ({ text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }), discord: (monitor, status) => ({ content: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }), telegram: (monitor, status) => ({ text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}` }) };
111-112
: LGTM! Clean implementation of webhook notifications.The changes effectively integrate webhook notifications while maintaining consistent error handling.
21-58
: Add test coverage for webhook notifications.The new webhook notification functionality should be covered by unit tests to ensure reliable operation.
Consider adding tests for:
- Different notification platforms
- Error scenarios
- Invalid Telegram credentials
- Network request failures
Would you like me to help generate the test cases?
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.
Looks pretty good! There's some odds and ends to clean up to get this up to production quality code and there's a problem with the Config schema that I missed last time.
See code review for details!
|
||
async triggerNotification(req, res, next) { | ||
try { | ||
const { monitorId, type, config } = req.body; |
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.
Validation should be done on this body at this level
Server/db/models/Notification.js
Outdated
webhookUrl: String, | ||
botToken: String, | ||
chatId: String | ||
}, |
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.
Aplogoies, I looked at this too quickly on my phone last time I reviewed. This is not the correct way to define an object schema with Mongoose. You need to define a separate schema for Config like
const configSchema = mongoose.Schema({
webhookUrl: {type: String}
...
})
Then you would use the sub-schema in the notificaiton schema
config: {
type: configSchema
default: {...}
}
You can see an example of this in the `HardwareCheck` schema file.
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.
All done!
Server/routes/notificationRoute.js
Outdated
|
||
next(); | ||
}; | ||
} |
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.
Lets move the validation to the Controller, all validation in this application is done at the controller level. Please do follow the try/catch
pattern of other validation schemas as well so we have predicatble error handling behaviour.
I don't think there's any reason to use middleware for this validation either. If we are going to use middleware for validation then all validation should be refactored to match. I don't think we want to do that right now, but perhaps in the future.
We should always follow convention as much as possible so we know where to look for things in the application.
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.
Complete.
Server/service/networkService.js
Outdated
'Content-Type': 'application/json' | ||
} | ||
}) | ||
); |
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.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. No need. Got rid of it.
@@ -9,13 +10,70 @@ 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Be sure to update the JSdoc since this now takes a networkService
parameter
const message = this.formatNotificationMessage(monitor, status, type, chatId); | ||
if (message === null) { | ||
this.logger.warn({ | ||
message: `Unsupported webhook type: ${type}`, |
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.
Make sure to use the localization service for user facing strings
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.
Done.
if (platform === 'discord') { | ||
return { content: messageText }; | ||
} | ||
return null; |
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.
Can you return undefined
here instead of null
? I'm trying to move towards using undefined
everywhere for emtpy data.
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.
Sure. All done.
} | ||
|
||
formatNotificationMessage(monitor, status, platform, chatId) { | ||
const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`; |
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.
Localization service neeed here
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.
Done.
formatNotificationMessage(monitor, status, platform, chatId) { | ||
const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`; | ||
|
||
if (platform === 'telegram') { |
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.
let's refactor these types into either an Array of acceptable types. This gives us the benefit of being able to easily add more types or change this value easily. Nothing worse than looking for hardcoded strings all over the place.
const PLATFORM_TYPES = ["telegram", "slack", "discord"];
Then you can just check if the value exists in the array or not.
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.
All done.
const { monitor, status } = networkResponse; | ||
const { type, webhookUrl, botToken, chatId } = config; | ||
let url = webhookUrl; | ||
|
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.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
Implemented.
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.
Actionable comments posted: 2
♻️ Duplicate comments (2)
Server/controllers/notificationController.js (1)
7-40
: 🛠️ Refactor suggestionAdd input validation before using request body fields.
The method uses destructured values from
req.body
without validating their presence, which could lead to runtime errors.async triggerNotification(req, res, next) { try { const { monitorId, type, platform, config } = req.body; + + if (!monitorId || !type || !platform || !config) { + throw new Error('Missing required fields'); + }Server/service/networkService.js (1)
438-481
: 🛠️ Refactor suggestionSimplify webhook request handling.
The timing of webhook requests adds unnecessary complexity as noted in previous reviews. Consider simplifying the implementation.
async requestWebhook(platform, url, message) { try { - const { response, responseTime, error } = await this.timeRequest(() => - this.axios.post(url, message, { - headers: { - 'Content-Type': 'application/json' - } - }) - ); + const response = await this.axios.post(url, message, { + headers: { + 'Content-Type': 'application/json' + } + }); const webhookResponse = { type: 'webhook', - responseTime, payload: response?.data };
🧹 Nitpick comments (4)
Server/service/notificationService.js (2)
21-34
: Refactor message formatting to be more flexible.The current implementation has hardcoded message format. Consider using a template system for better maintainability and customization.
+ const MESSAGE_TEMPLATES = { + up: 'Monitor {name} is up. URL: {url}', + down: 'Monitor {name} is down. URL: {url}' + }; + formatNotificationMessage(monitor, status, platform, chatId) { - const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`; + const template = MESSAGE_TEMPLATES[status ? 'up' : 'down']; + const messageText = template + .replace('{name}', monitor.name) + .replace('{url}', monitor.url);
36-76
: Improve error handling specificity.The error handling could be more specific to help with debugging different types of webhook failures.
} catch (error) { this.logger.error({ - message: `Error sending ${platform} notification`, + message: `Error sending ${platform} notification: ${error.code === 'ECONNREFUSED' ? 'Connection refused' : 'Request failed'}`, service: this.SERVICE_NAME, method: 'sendWebhookNotification', error: error.message,Server/validation/joi.js (2)
478-488
: Enhance webhook URL validation.The current URL validation is basic. Consider adding specific validation for each platform's webhook URL format.
webhookUrl: joi.string().uri().required().messages({ 'string.empty': 'Discord webhook URL is required', 'string.uri': 'Discord webhook URL must be a valid URL', - 'any.required': 'Discord webhook URL is required' + 'any.required': 'Discord webhook URL is required', + 'string.pattern.base': 'Invalid Discord webhook URL format' + }).pattern(/^https:\/\/discord\.com\/api\/webhooks\/[\d]+\/[\w-]+$/),Also applies to: 490-498, 500-508
510-545
: Centralize platform validation.The platform validation is duplicated. Consider extracting it to a constant.
+const SUPPORTED_PLATFORMS = ['telegram', 'discord', 'slack']; + const triggerNotificationBodyValidation = joi.object({ // ... - platform: joi.string().valid('telegram', 'discord', 'slack').required().messages({ + platform: joi.string().valid(...SUPPORTED_PLATFORMS).required().messages({🧰 Tools
🪛 Biome (1.9.4)
[error] 530-530: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 534-534: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 538-538: Do not add then to an object.
(lint/suspicious/noThenProperty)
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
Server/controllers/notificationController.js
(1 hunks)Server/db/models/Notification.js
(3 hunks)Server/service/networkService.js
(1 hunks)Server/service/notificationService.js
(3 hunks)Server/validation/joi.js
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Server/db/models/Notification.js
🧰 Additional context used
🪛 Biome (1.9.4)
Server/validation/joi.js
[error] 530-530: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 534-534: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 538-538: Do not add then to an object.
(lint/suspicious/noThenProperty)
class NotificationController { | ||
constructor(notificationService) { | ||
this.notificationService = notificationService; | ||
this.triggerNotification = this.triggerNotification.bind(this); | ||
} |
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.
🛠️ Refactor suggestion
Add JSDoc documentation for the class and constructor.
Missing documentation makes it harder for other developers to understand the purpose and usage of this class.
+/**
+ * Controller for handling notification triggers.
+ * @class NotificationController
+ */
class NotificationController {
+ /**
+ * Creates an instance of NotificationController.
+ * @param {Object} notificationService - Service for sending notifications
+ */
constructor(notificationService) {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class NotificationController { | |
constructor(notificationService) { | |
this.notificationService = notificationService; | |
this.triggerNotification = this.triggerNotification.bind(this); | |
} | |
/** | |
* Controller for handling notification triggers. | |
* @class NotificationController | |
*/ | |
class NotificationController { | |
/** | |
* Creates an instance of NotificationController. | |
* @param {Object} notificationService - Service for sending notifications | |
*/ | |
constructor(notificationService) { | |
this.notificationService = notificationService; | |
this.triggerNotification = this.triggerNotification.bind(this); | |
} | |
} |
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.
Actionable comments posted: 2
♻️ Duplicate comments (2)
Server/routes/notificationRoute.js (1)
12-16
:⚠️ Potential issueYo dawg, we need to protect this endpoint from abuse! 🔒
The
/trigger
endpoint needs rate limiting to prevent notification spam. Even with that sweet JWT verification, authenticated users could still flood those Discord channels faster than Eminem's rap speed!Add rate limiting middleware before the route handler:
this.router.post( '/trigger', verifyJWT, + rateLimiter({ + windowMs: 60 * 1000, // 1 minute + max: 10 // limit each IP to 10 requests per windowMs + }), this.notificationController.triggerNotification );🧰 Tools
🪛 GitHub Check: CodeQL
[failure] 14-14: Missing rate limiting
This route handler performs authorization, but is not rate-limited.Server/controllers/notificationController.js (1)
31-36
:⚠️ Potential issueMom's spaghetti alert! 🍝 These values are hardcoded!
The
networkResponse
object has hardcoded monitor details that'll make your knees weak in production!- const networkResponse = { - monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, - status: false, - statusChanged: true, - prevStatus: true, - }; + const monitor = await this.monitorService.getMonitorById(monitorId); + const networkResponse = { + monitor: { _id: monitorId, name: monitor.name, url: monitor.url }, + status: false, + statusChanged: true, + prevStatus: true, + };
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Server/controllers/notificationController.js
(1 hunks)Server/routes/notificationRoute.js
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
Server/routes/notificationRoute.js
[failure] 14-14: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
Server/controllers/notificationController.js (1)
11-15
: Spit some documentation bars! 📝Your code's looking clean, but it needs some documentation swagger!
+/** + * Controller for handling notification triggers across different platforms. + * @class NotificationController + */ class NotificationController { + /** + * Creates an instance of NotificationController. + * @param {Object} notificationService - Service for sending notifications + */ constructor(notificationService) {
this.router.post( | ||
'/trigger', | ||
verifyJWT, | ||
this.notificationController.triggerNotification |
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.
Lose yourself in the proper binding! 🎵
The controller method needs to be bound to maintain its context, just like you need to bind yourself to the beat!
- this.notificationController.triggerNotification
+ this.notificationController.triggerNotification.bind(this.notificationController)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
this.notificationController.triggerNotification | |
this.notificationController.triggerNotification.bind(this.notificationController) |
if (type === "webhook") { | ||
const notification = { | ||
type, | ||
platform, | ||
config | ||
}; | ||
|
||
await this.notificationService.sendWebhookNotification( | ||
networkResponse, | ||
notification | ||
); | ||
} |
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.
Drop the beat with platform-specific validation! 🎤
You've imported those validation schemas like they're backup dancers, but they're not getting any stage time! Let's make them perform!
if (type === "webhook") {
+ let validationSchema;
+ switch (platform) {
+ case 'discord':
+ validationSchema = discordWebhookConfigValidation;
+ break;
+ case 'telegram':
+ validationSchema = telegramWebhookConfigValidation;
+ break;
+ case 'slack':
+ validationSchema = slackWebhookConfigValidation;
+ break;
+ default:
+ throw new Error(`Unsupported platform: ${platform}`);
+ }
+
+ await validationSchema.validateAsync(config, {
+ abortEarly: false,
+ stripUnknown: true
+ });
const notification = {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (type === "webhook") { | |
const notification = { | |
type, | |
platform, | |
config | |
}; | |
await this.notificationService.sendWebhookNotification( | |
networkResponse, | |
notification | |
); | |
} | |
if (type === "webhook") { | |
let validationSchema; | |
switch (platform) { | |
case 'discord': | |
validationSchema = discordWebhookConfigValidation; | |
break; | |
case 'telegram': | |
validationSchema = telegramWebhookConfigValidation; | |
break; | |
case 'slack': | |
validationSchema = slackWebhookConfigValidation; | |
break; | |
default: | |
throw new Error(`Unsupported platform: ${platform}`); | |
} | |
await validationSchema.validateAsync(config, { | |
abortEarly: false, | |
stripUnknown: true | |
}); | |
const notification = { | |
type, | |
platform, | |
config | |
}; | |
await this.notificationService.sendWebhookNotification( | |
networkResponse, | |
notification | |
); | |
} |
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.
Actionable comments posted: 0
♻️ Duplicate comments (2)
Server/controllers/notificationController.js (1)
39-50
:⚠️ Potential issueDrop the validation beat! 🎵
Platform-specific validation is missing. Each platform (Discord, Telegram, Slack) has its own validation schema that should be used.
Here's how to validate that config:
if (type === "webhook") { + let validationSchema; + switch (platform) { + case 'discord': + validationSchema = discordWebhookConfigValidation; + break; + case 'telegram': + validationSchema = telegramWebhookConfigValidation; + break; + case 'slack': + validationSchema = slackWebhookConfigValidation; + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + + await validationSchema.validateAsync(config, { + abortEarly: false, + stripUnknown: true + });Server/index.js (1)
326-326
:⚠️ Potential issueProtect that endpoint with rate limiting! 🛡️
The notification routes need rate limiting to prevent abuse.
Add rate limiting middleware before the route registration:
+ const rateLimit = require('express-rate-limit'); + + const notificationLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100 // limit each IP to 100 requests per windowMs + }); + - app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); + app.use("/api/v1/notifications", verifyJWT, notificationLimiter, notificationRoutes.getRouter());🧰 Tools
🪛 GitHub Check: CodeQL
[failure] 326-326: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
🧹 Nitpick comments (3)
Server/controllers/notificationController.js (1)
1-62
: Protect your endpoints like mom's spaghetti! 🍝The notification endpoint needs rate limiting to prevent abuse.
Add rate limiting middleware to protect against potential DoS attacks. Consider using
express-rate-limit
:const rateLimit = require('express-rate-limit'); const notificationLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); // Apply to notification routes app.use('/api/v1/notifications', notificationLimiter);Server/service/notificationService.js (2)
2-2
: Move that URL to a constants file! 🎵The
TELEGRAM_API_BASE_URL
should be moved to a shared constants file for better maintainability.Create a new file
constants.js
:export const API_URLS = { TELEGRAM: 'https://api.telegram.org/bot' };
35-52
: Make those platforms an enum, yo! 🎤Platform types should be defined as an enum or constant array for better maintainability and type safety.
Add this at the top of the file:
const SUPPORTED_PLATFORMS = ['telegram', 'slack', 'discord'];Then update the validation:
- if (platform === 'telegram') { + if (!SUPPORTED_PLATFORMS.includes(platform)) { + return null; + } + + const messageFormatters = { + telegram: (text) => ({ chat_id: chatId, text }), + slack: (text) => ({ text }), + discord: (text) => ({ content: text }) + }; + + return messageFormatters[platform](messageText);
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
Server/controllers/notificationController.js
(1 hunks)Server/index.js
(5 hunks)Server/service/notificationService.js
(3 hunks)Server/service/stringService.js
(1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
Server/index.js
[failure] 326-326: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
🔇 Additional comments (2)
Server/controllers/notificationController.js (1)
32-37
: Yo dawg, those monitor details are hardcoded! 🍝The
networkResponse
object contains hardcoded values which could cause issues in production. These values should be fetched from the database.Server/service/stringService.js (1)
168-196
: Clean string service implementation! 🎤The webhook message formatting implementation is well-structured with proper i18n support and string templating.
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.
Actionable comments posted: 1
🧹 Nitpick comments (4)
Server/service/notificationService.js (4)
2-2
: Move API base URL to configuration.The Telegram API base URL should be moved to a configuration file for better maintainability.
Create a new file
config/api.js
:export const API_URLS = { TELEGRAM: 'https://api.telegram.org/bot' };Then update the import:
+import { API_URLS } from '../config/api.js'; -const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot";
35-52
: LGTM, but consider using a map for platform-specific message formats.The message formatting logic is clean and uses the string service correctly, but could be more maintainable with a map.
Consider this refactor:
- formatNotificationMessage(monitor, status, platform, chatId) { - const messageText = this.stringService.getMonitorStatus( - monitor.name, - status, - monitor.url - ); - - if (platform === 'telegram') { - return { chat_id: chatId, text: messageText }; - } - if (platform === 'slack') { - return { text: messageText }; - } - if (platform === 'discord') { - return { content: messageText }; - } - return undefined; - } + formatNotificationMessage(monitor, status, platform, chatId) { + const messageText = this.stringService.getMonitorStatus( + monitor.name, + status, + monitor.url + ); + + const formatters = { + telegram: (msg) => ({ chat_id: chatId, text: msg }), + slack: (msg) => ({ text: msg }), + discord: (msg) => ({ content: msg }) + }; + + return formatters[platform]?.(messageText); + }
69-109
: Add retry mechanism for webhook notifications.The webhook notification implementation is solid but could benefit from a retry mechanism for transient failures.
Consider adding a retry mechanism:
+ async retryWebhook(fn, retries = 3, delay = 1000) { + for (let i = 0; i < retries; i++) { + try { + return await fn(); + } catch (error) { + if (i === retries - 1) throw error; + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + } async sendWebhookNotification(networkResponse, notification) { // ... existing code ... try { - const response = await this.networkService.requestWebhook(platform, url, message); + const response = await this.retryWebhook(() => + this.networkService.requestWebhook(platform, url, message) + ); return response.status; } catch (error) { // ... existing error handling ... } }
159-166
: Consider using Promise.all for parallel notification processing.The notifications are being processed sequentially, which could be slow for a large number of notifications.
Consider this optimization:
- for (const notification of notifications) { - if (notification.type === "email") { - await this.sendEmail(networkResponse, notification.address); - } else if (notification.type === "webhook") { - await this.sendWebhookNotification(networkResponse, notification); - } - // Handle other types of notifications here - } + await Promise.all(notifications.map(notification => { + if (notification.type === "email") { + return this.sendEmail(networkResponse, notification.address); + } else if (notification.type === "webhook") { + return this.sendWebhookNotification(networkResponse, notification); + } + return Promise.resolve(); + }));
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Server/index.js
(5 hunks)Server/service/networkService.js
(1 hunks)Server/service/notificationService.js
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Server/service/networkService.js
🧰 Additional context used
🪛 GitHub Check: CodeQL
Server/index.js
[failure] 308-308: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
Server/index.js (4)
41-43
: LGTM!The imports are correctly organized and follow the established pattern in the file.
173-173
: LGTM!The NotificationService constructor is correctly updated with the required dependencies.
258-261
: LGTM!The NotificationController is properly instantiated with the required services.
284-284
: LGTM!The NotificationRoutes is properly instantiated with the controller.
@@ -293,6 +305,7 @@ | |||
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", verifyJWT, notificationRoutes.getRouter()); |
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.
Add rate limiting to protect against abuse.
The notification route is properly secured with JWT verification, but it lacks rate limiting which could make it vulnerable to abuse.
Apply this diff to add rate limiting:
-app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
+app.use("/api/v1/notifications", verifyJWT, rateLimiter, notificationRoutes.getRouter());
And add the rate limiter middleware at the top of the file:
import rateLimit from 'express-rate-limit';
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
🧰 Tools
🪛 GitHub Check: CodeQL
[failure] 308-308: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
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.
Actionable comments posted: 0
🧹 Nitpick comments (4)
Server/service/notificationService.js (4)
2-2
: Yo, let's move this API URL to a config file!The Telegram API base URL should be moved to a configuration file for better maintainability and flexibility across different environments.
14-20
: Heads up! JSDoc's missing the stringService param, fam!Update the JSDoc to include the
stringService
parameter description.* @param {Object} db - The database instance for storing notification data. * @param {Object} logger - The logger instance for logging activities. * @param {Object} networkService - The network service for sending webhook notifications. + * @param {Object} stringService - The string service for localization and message formatting. */
90-90
: Let's keep it consistent with the localization service, yo!This hardcoded error message should use the stringService like other messages in the file.
- message: 'Missing required fields for Telegram notification', + message: this.stringService.getTelegramMissingFieldsError(),
182-187
: Let's beef up that error logging, dawg!Add more context to the error log by including the networkResponse details (excluding sensitive data).
this.logger.warn({ message: error.message, service: this.SERVICE_NAME, method: "handleNotifications", stack: error.stack, + monitorId: networkResponse.monitorId, + statusChanged: networkResponse.statusChanged, + notifications: notifications?.length });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Server/service/notificationService.js
(3 hunks)
🔇 Additional comments (1)
Server/service/notificationService.js (1)
42-54
: Yo, this implementation is fire! 🔥Clean implementation with:
- Early returns for invalid platforms
- Proper use of localization service
- Efficient message formatting using the MESSAGE_FORMATTERS object
const { monitorId, type, platform, config } = req.body; | ||
|
||
const networkResponse = { | ||
monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, |
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 see the name and url in networkResponse object are hardcoded. Was this just for testing?
import express from 'express'; | ||
import { verifyJWT } from '../middleware/verifyJWT.js'; | ||
|
||
class NotificationRoutes { | ||
constructor(notificationController) { | ||
this.notificationController = notificationController; | ||
this.router = express.Router(); | ||
this.initializeRoutes(); | ||
} | ||
|
||
initializeRoutes() { | ||
this.router.post( | ||
'/trigger', | ||
verifyJWT, |
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.
Looks good to me.
async requestWebhook(platform, url, message) { | ||
try { | ||
const response = await this.axios.post(url, message, { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}); | ||
|
||
return { | ||
type: 'webhook', | ||
status: true, | ||
code: response.status, | ||
message: `Successfully sent ${platform} notification`, | ||
payload: response.data | ||
}; | ||
|
||
} catch (error) { | ||
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 { | ||
type: 'webhook', | ||
status: false, | ||
code: error.response?.status || this.NETWORK_ERROR, | ||
message: `Failed to send ${platform} notification`, | ||
payload: error.response?.data | ||
}; | ||
} | ||
} | ||
|
||
|
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.
error handling and logging looks good.
Server/validation/joi.js
Outdated
const telegramWebhookConfigValidation = joi.object({ | ||
webhookUrl: joi.string().uri().optional(), | ||
botToken: joi.string().required().messages({ | ||
'string.empty': 'Telegram bot token is required', | ||
'any.required': 'Telegram bot token is required' | ||
}), | ||
chatId: joi.string().required().messages({ | ||
'string.empty': 'Telegram chat ID is required', | ||
'any.required': 'Telegram chat ID is required' | ||
}) |
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.
Validation looks solid! Just wondering , if only botToken and chatID are used for telegram, can we remove the webhookurl?
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 think we're mostly good here.
We can just use one body validation for the webhook config, and we don't need the conditional in the webhook trigger body validation.
There's a string that needs to be refactored to string service, and some unused imports to remove.
I think once that's done we're good to go.
Server/validation/joi.js
Outdated
}), | ||
botToken: joi.string().optional(), | ||
chatId: joi.string().optional() | ||
}); |
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.
We don't need three different validation schemas, one is sufficient since they are all the same
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.
Done.
triggerNotificationBodyValidation, | ||
telegramWebhookConfigValidation, | ||
discordWebhookConfigValidation, | ||
slackWebhookConfigValidation |
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.
These validations are not used here, they can be removed
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.
Done.
Server/validation/joi.js
Outdated
'any.only': 'Platform must be telegram, discord, or slack' | ||
}), | ||
config: joi.alternatives() | ||
.conditional('platform', [ |
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.
Then we can simplify here and we don't need this conditional since we only have one webhook config
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.
Done.
} | ||
|
||
return res.success({ | ||
msg: "Notification sent successfully" |
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.
We need to use the string service here
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.
Done.
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
Server/controllers/notificationController.js (1)
29-34
:⚠️ Potential issueYo, those hardcoded values gotta go! 🚫
Replace the hardcoded test values with actual monitor data from the database.
const networkResponse = { - monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, + monitor: await this.notificationService.getMonitorById(monitorId), status: false, statusChanged: true, prevStatus: true, };
🧹 Nitpick comments (3)
Server/controllers/notificationController.js (2)
8-13
: Yo dawg, let's drop some documentation beats! 🎵Add JSDoc documentation to help other devs understand this class better.
+/** + * Controller for handling notification triggers and webhook integrations. + */ class NotificationController { + /** + * Creates an instance of NotificationController. + * @param {Object} notificationService - Service for sending notifications + * @param {Object} stringService - Service for string operations + */ constructor(notificationService, stringService) {
36-47
: Let's catch those errors with style! 🎭Add specific error handling for webhook failures to provide better error messages.
if (type === "webhook") { + try { const notification = { type, platform, config }; await this.notificationService.sendWebhookNotification( networkResponse, notification ); + } catch (error) { + throw new Error(`Failed to send ${platform} webhook notification: ${error.message}`); + } }Server/validation/joi.js (1)
478-522
: Let's simplify this validation flow! 🌊The current implementation with multiple switch cases could be simplified using a more direct approach.
const webhookConfigValidation = joi.object({ - webhookUrl: joi.string().uri() - .when('$platform', { - switch: [ - { - is: 'telegram', - then: joi.optional() - }, - { - is: 'discord', - then: joi.required().messages({ - 'string.empty': 'Discord webhook URL is required', - 'string.uri': 'Discord webhook URL must be a valid URL', - 'any.required': 'Discord webhook URL is required' - }) - }, - { - is: 'slack', - then: joi.required().messages({ - 'string.empty': 'Slack webhook URL is required', - 'string.uri': 'Slack webhook URL must be a valid URL', - 'any.required': 'Slack webhook URL is required' - }) - } - ] - }), + webhookUrl: joi.string().uri().when('$platform', { + is: 'telegram', + then: joi.optional(), + otherwise: joi.required().messages({ + 'string.empty': '${$platform} webhook URL is required', + 'string.uri': '${$platform} webhook URL must be a valid URL', + 'any.required': '${$platform} webhook URL is required' + }) + }), botToken: joi.string()🧰 Tools
🪛 Biome (1.9.4)
[error] 484-484: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 488-488: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 496-496: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 507-507: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 516-516: Do not add then to an object.
(lint/suspicious/noThenProperty)
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Server/controllers/notificationController.js
(1 hunks)Server/validation/joi.js
(2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
Server/validation/joi.js
[error] 484-484: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 488-488: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 496-496: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 507-507: Do not add then to an object.
(lint/suspicious/noThenProperty)
[error] 516-516: Do not add then to an object.
(lint/suspicious/noThenProperty)
🔇 Additional comments (2)
Server/controllers/notificationController.js (1)
15-24
: Clean validation implementation! 🎯Good use of joi validation with proper error handling through middleware.
Server/validation/joi.js (1)
524-542
: Validation schema's lookin' fresh! 🎵Solid implementation with comprehensive validation and clear error messages.
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.
Actionable comments posted: 1
♻️ Duplicate comments (2)
Server/controllers/notificationController.js (2)
36-47
:⚠️ Potential issueTime to validate those platform configs! 🎵
We need to add platform-specific validation before sending those webhook notifications.
if (type === "webhook") { + const validationSchema = { + discord: discordWebhookConfigValidation, + telegram: telegramWebhookConfigValidation, + slack: slackWebhookConfigValidation + }[platform]; + + if (!validationSchema) { + throw new Error(`Unsupported platform: ${platform}`); + } + + await validationSchema.validateAsync(config, { + abortEarly: false, + stripUnknown: true + }); const notification = {
29-34
:⚠️ Potential issueDrop those hardcoded beats, fam! 🎧
The monitor details are hardcoded - that's not how we do it in production! Let's get that real monitor data flowing.
const networkResponse = { - monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, + monitor: await this.monitorService.getMonitorById(monitorId), status: false, statusChanged: true, prevStatus: true, };
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Server/controllers/notificationController.js
(1 hunks)
🔇 Additional comments (2)
Server/controllers/notificationController.js (2)
8-13
: Straight fire constructor! 🔥Clean dependency injection with both services and proper method binding. That's how we roll!
59-59
: Clean export, we out! 🎤Export looking clean and following our conventions.
import { | ||
triggerNotificationBodyValidation, | ||
} from '../validation/joi.js'; | ||
import { handleError, handleValidationError } from './controllerUtils.js'; |
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.
Yo, where's the platform validation squad at? 🎤
We need to import those platform-specific validation schemas to keep our webhooks tight and secure!
import {
triggerNotificationBodyValidation,
+ discordWebhookConfigValidation,
+ telegramWebhookConfigValidation,
+ slackWebhookConfigValidation
} from '../validation/joi.js';
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
import { | |
triggerNotificationBodyValidation, | |
} from '../validation/joi.js'; | |
import { handleError, handleValidationError } from './controllerUtils.js'; | |
import { | |
triggerNotificationBodyValidation, | |
discordWebhookConfigValidation, | |
telegramWebhookConfigValidation, | |
slackWebhookConfigValidation | |
} from '../validation/joi.js'; | |
import { handleError, handleValidationError } from './controllerUtils.js'; |
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.
Thanks for making all the revisions!
Describe your changes
Add Discord webhook notification support
Issue number
#1545
Please ensure all items are checked off before requesting a review: