Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Discord webhook notification support #1612

Open
wants to merge 29 commits into
base: develop
Choose a base branch
from

Conversation

Skorpios604
Copy link
Member

Describe your changes

Add Discord webhook notification support

Issue number

#1545

Please ensure all items are checked off before requesting a review:

  • I deployed the application locally.
  • I have performed a self-review and testing of my code.
  • I have included the issue # in the PR.
  • I have labelled the PR correctly.
  • The issue I am working on is assigned to me.
  • I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
  • I made sure font sizes, color choices etc. are all referenced from the theme.
  • My PR is granular and targeted to one specific feature.
  • I took a screenshot or a video and attached to this PR if there is a UI change.

Copy link

coderabbitai bot commented Jan 22, 2025

Walkthrough

The pull request enhances the notification system by modifying the NotificationSchema to include a new config field with properties for different notification types. It introduces methods in the NotificationService for sending notifications via Telegram, Discord, and Slack, along with error handling. The server structure is updated to incorporate a new NotificationController for processing requests and a NotificationRoutes class for defining routing. Additionally, a requestWebhook method is added to the NetworkService for handling webhook requests.

Changes

File Change Summary
Server/db/models/Notification.js Modified NotificationSchema to include type: String, enum: ["email", "sms", "webhook"] and added config: { webhookUrl: String, botToken: String, chatId: String }.
Server/service/notificationService.js Introduced TELEGRAM_API_BASE_URL; added async sendWebhookNotification(networkResponse, config) and formatNotificationMessage(monitor, status, platform, chatId) methods; updated constructor to include networkService; modified handleStatusNotifications to call sendWebhookNotification.
Server/index.js Added imports for NotificationRoutes and NotificationController; updated notificationService instantiation to include networkService; created and registered notificationController; added route for notifications at /api/v1/notifications.
Server/service/networkService.js Added async requestWebhook(platform, url, message) method for sending POST requests to webhooks with error logging.
Server/controllers/notificationController.js Introduced NotificationController class; added async triggerNotification(req, res, next) method for processing notification requests with validation.
Server/routes/notificationRoute.js Introduced NotificationRoutes class; added methods for initializing routes and returning the router.
Server/validation/joi.js Added validation schemas for webhook configurations and notification triggers, ensuring required fields are validated.

Sequence Diagram

sequenceDiagram
    participant Service as NotificationService
    participant Discord as Discord Webhook
    participant Slack as Slack Webhook
    participant Telegram as Telegram Webhook
    participant Logger as Error Logger

    Service->>Discord: POST notification message
    alt Successful Discord notification
        Discord-->>Service: Successful response
    else Discord notification failed
        Service->>Logger: Log Discord error details
    end

    Service->>Slack: POST notification message
    alt Successful Slack notification
        Slack-->>Service: Successful response
    else Slack notification failed
        Service->>Logger: Log Slack error details
    end

    Service->>Telegram: POST notification message
    alt Successful Telegram notification
        Telegram-->>Service: Successful response
    else Telegram notification failed
        Service->>Logger: Log Telegram error details
    end
Loading

The sequence diagram illustrates the process of sending notifications to Discord, Slack, and Telegram, including success and error handling paths for each communication channel.

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between d3ad898 and 28f5d35.

📒 Files selected for processing (2)
  • Server/db/models/Notification.js (1 hunks)
  • Server/service/notificationService.js (2 hunks)

@@ -8,7 +8,7 @@ const NotificationSchema = mongoose.Schema(
},
type: {
type: String,
enum: ["email", "sms"],
enum: ["email", "sms", "discord"],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Copy link

@llamapreview llamapreview bot left a 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 function sendDiscordNotification in the notificationService 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 the notificationService.
  • 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 the axios.post call, logs it, and returns false.
    • 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 and error.stack, which is good for debugging, but it could be enhanced with more context, such as the webhookUrl being used (obfuscated for security in production logs, but helpful in development/debugging) and the monitor.name to easily identify the source of the notification failure.
  • 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.

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 takes webhookUrl 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 the axios.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).
  • 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.

2.2 Implementation Quality

  • Code organization and structure: The code is well-organized, with the new sendDiscordNotification function clearly defined within the notificationService. The addition of axios 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.
  • 🟡 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.

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

  1. Implement secure storage, management, and validation of webhook URLs.
  2. Enhance error logging with more context, such as the monitor name and a truncated webhook URL.
  3. Consider implementing retry logic for transient errors to improve the reliability of notifications.
  4. 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!

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 28f5d35 and 172597e.

📒 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:

  1. No webhook URL validation
  2. Basic message format (Discord supports rich embeds)
  3. No rate limiting consideration
  4. No retry mechanism for failed requests

40-58: 🛠️ Refactor suggestion

Mom'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.

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 23, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 23, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 23, 2025
Copy link
Collaborator

@ajhollid ajhollid left a 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;
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Complete.

method: "sendDiscordNotification",
stack: error.stack,
});
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes!

@@ -1,4 +1,5 @@
const SERVICE_NAME = "NotificationService";
import axios from 'axios';
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

}
}

async sendSlackNotification(networkResponse, webhookUrl) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

We can simplify and just simply have

sendWebHook({webHookUrl, msg, networkResponse})

All webhooks are going to be pretty much the same

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Duplicate 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

📥 Commits

Reviewing files that changed from the base of the PR and between 172597e and d3810db.

📒 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 issue

Mom's spaghetti moment - we got some cleanup to do here!

Several issues need addressing:

  1. Network operations should live in NetworkService
  2. Silent error handling needs improvement - either propagate or handle explicitly
  3. 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.

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 26, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 26, 2025
Copy link
Collaborator

@ajhollid ajhollid left a 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 👍

Server/service/notificationService.js Show resolved Hide resolved
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`,
};

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

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

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

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

Choose a reason for hiding this comment

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

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between d3810db and 84fe58b.

📒 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 issue

There's vomit on the sweater already - these methods don't exist! 🍝

The code references sendDiscordNotification, sendSlackNotification, and sendTelegramNotification, but these methods aren't implemented. Should be using the new sendWebhookNotification 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.

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 27, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 27, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Jan 27, 2025
Copy link
Collaborator

@ajhollid ajhollid left a 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`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

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

} 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());
Copy link
Collaborator

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Complete.

chat_id: chatId,
text: `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`
};
url = `https://api.telegram.org/bot${botToken}/sendMessage`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will be unnecessary once the token and chat ID are moved to their own fields

Copy link
Member Author

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}` };
Copy link
Collaborator

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.

Copy link
Member Author

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);
Copy link
Collaborator

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

Copy link
Member Author

Choose a reason for hiding this comment

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

All done.

Server/service/notificationService.js Show resolved Hide resolved
} else if (notification.type === "slack") {
this.sendSlackNotification(networkResponse, notification.address);
} else if (notification.type === "telegram") {
this.sendTelegramNotification(networkResponse, notification.address);
Copy link
Collaborator

Choose a reason for hiding this comment

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

these should all be updated to use the webhook function?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

}

try {
await axios.post(url, message, {
Copy link
Collaborator

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Refactor webhook message construction and improve security.

Several improvements needed:

  1. Token and chatId should be stored in dedicated fields rather than concatenated in the address field
  2. Message templates can be extracted to reduce duplication
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 84fe58b and cc48f1d.

📒 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

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between cc48f1d and 0bb7eae.

📒 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 suggestion

Yo dawg, let's make this webhook code even more fire! 🎵

A few suggestions to level up this implementation:

  1. 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}`);
+   }
+ };
  1. 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;
+ }
  1. 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

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Move 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 suggestion

Refactor 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0bb7eae and d3fb41a.

📒 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?

Copy link

@coderabbitai coderabbitai bot left a 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

🔭 Outside diff range comments (1)
Server/db/models/Notification.js (1)

9-12: Yo, the enum's not keeping it real with the new notification types! 🍝

The type enum is missing the new notification types that we're validating in the pre-save middleware.

Drop this beat to fix the enum:

 type: {
   type: String,
-  enum: ["email", "sms"],
+  enum: ["email", "sms", "telegram", "discord", "slack"],
 },
🧹 Nitpick comments (2)
Server/db/models/Notification.js (1)

13-17: Config object's structure is clean, but needs some validation swagger! 🎤

Add some URL validation for those webhook URLs to keep it legit.

Here's the remix:

 config: {
-  webhookUrl: { type: String }, // For Discord & Slack
+  webhookUrl: {
+    type: String,
+    validate: {
+      validator: function(v) {
+        return !["discord", "slack"].includes(this.type) || 
+               /^https:\/\/.*$/.test(v);
+      },
+      message: "Invalid webhook URL format"
+    }
+  },
   botToken: { type: String }, // For Telegram
   chatId: { type: String }, // For Telegram
 },
Server/controllers/notificationController.js (1)

26-54: These validation blocks are repeating themselves, time for a cleanup! 🧹

The validation logic for different notification types is repetitive. Let's make it DRY.

Here's a cleaner flow:

+    const CONFIG_REQUIREMENTS = {
+        telegram: ['botToken', 'chatId'],
+        discord: ['webhookUrl'],
+        slack: ['webhookUrl'],
+        email: ['address']
+    };
+
+    const validateConfig = (type, config) => {
+        const required = CONFIG_REQUIREMENTS[type];
+        if (!required) return null;
+        
+        const missing = required.filter(field => !config?.[field]);
+        if (missing.length > 0) {
+            return `${missing.join(' and ')} are required for ${type} notifications`;
+        }
+        return null;
+    };

-    if (type === "telegram") {
-        if (!config?.botToken || !config?.chatId) {
-            return res.status(400).json({ 
-                success: false, 
-                msg: "botToken and chatId are required for Telegram notifications" 
-            });
-        }
+    const validationError = validateConfig(type, config);
+    if (validationError) {
+        return res.status(400).json({
+            success: false,
+            msg: validationError
+        });
+    }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64c5aa9 and d8379f7.

📒 Files selected for processing (2)
  • Server/controllers/notificationController.js (1 hunks)
  • Server/db/models/Notification.js (3 hunks)
🔇 Additional comments (2)
Server/controllers/notificationController.js (2)

19-24: Mom's spaghetti alert! 🍝 Still got that hardcoded test data!

The hardcoded test monitor data needs to go, as mentioned in the previous review. This could cause issues in production.


58-63: The error logging is straight fire! 🔥

Clean structured logging with all the necessary details. Keep it up!

Comment on lines 66 to 68
if (this.type === "email" && !this.config.address) {
return next(new Error("address is required for email notifications"));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo, this validation's got a bug in its flow! 🐛

The validation is checking config.address but the address field is at the root level.

Fix the validation like this:

-if (this.type === "email" && !this.config.address) {
+if (this.type === "email" && !this.address) {
   return next(new Error("address is required for email notifications"));
 }
📝 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.

Suggested change
if (this.type === "email" && !this.config.address) {
return next(new Error("address is required for email notifications"));
}
if (this.type === "email" && !this.address) {
return next(new Error("address is required for email notifications"));
}

Copy link

@coderabbitai coderabbitai bot left a 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 (3)
Server/controllers/notificationController.js (3)

11-16: Yo, let's beef up that validation game!

The basic checks are cool, but we could make it even better by adding type validation:

-        if (!monitorId || !type) {
+        if (!monitorId || typeof monitorId !== 'string' || !type || typeof type !== 'string') {
             return res.status(400).json({ 
                 success: false, 
-                msg: "monitorId and type are required" 
+                msg: "monitorId and type are required and must be strings" 
             });
         }

26-54: Yo, let's level up this webhook handling!

The validation's tight, but we could make it even better with some improvements:

             if (type === "webhook") {
+                const SUPPORTED_WEBHOOK_TYPES = ["telegram", "discord", "slack"];
                 if (!config?.type) {
                     return res.status(400).json({ 
                         success: false, 
-                        msg: "webhook type is required in config" 
+                        msg: `webhook type is required in config. Supported types: ${SUPPORTED_WEBHOOK_TYPES.join(", ")}` 
                     });
                 }
+                if (!SUPPORTED_WEBHOOK_TYPES.includes(config.type)) {
+                    return res.status(400).json({
+                        success: false,
+                        msg: `Unsupported webhook type. Supported types: ${SUPPORTED_WEBHOOK_TYPES.join(", ")}`
+                    });
+                }

57-65: Let's make these error messages more fire! 🔥

The error handling's decent, but we could make it more specific to help with debugging:

         } catch (error) {
+            const errorMessage = error.code === 'WEBHOOK_TIMEOUT' 
+                ? 'Webhook request timed out' 
+                : error.message;
             logger.error({
-                message: error.message,
+                message: errorMessage,
                 service: "NotificationController",
                 method: "triggerNotification",
+                errorCode: error.code,
                 stack: error.stack,
             });
-            res.status(500).json({ success: false, msg: "Failed to send notification" });
+            res.status(500).json({ 
+                success: false, 
+                msg: `Failed to send notification: ${errorMessage}` 
+            });
         }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8379f7 and f46b076.

📒 Files selected for processing (3)
  • Server/controllers/notificationController.js (1 hunks)
  • Server/db/models/Notification.js (2 hunks)
  • Server/service/notificationService.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Server/db/models/Notification.js
  • Server/service/notificationService.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
Server/controllers/notificationController.js (2)

4-6: Yo dawg, we still need that monitor service injection!

The controller's still running without its monitor service buddy! Let's fix that up real quick!


19-24: Bruh, that hardcoded data's still chillin' here!

We gotta get rid of this test data, it's not production-ready!

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 10, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 10, 2025
Copy link
Collaborator

@ajhollid ajhollid left a comment

Choose a reason for hiding this comment

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

Looking pretty good!

We need to secure the notification route and be sure to use response and error handling middleware.

Hardcoded strings should be refactored too. You may want to wait on this until after tomorrow as there is an open PR regarding localisation that will be merged in very soon.

Thanks for working on it!

return res.status(400).json({
success: false,
msg: "webhook type is required in config"
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need to do manual validation here, validation should be done automatically via joi as with other requests

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

return res.status(400).json({
success: false,
msg: `webhookUrl is required for ${config.type} notifications`
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use the response handling middleware to return a resonse. All responses should go through the middleware to enforce standard response format.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

method: "triggerNotification",
stack: error.stack,
});
res.status(500).json({ success: false, msg: "Failed to send notification" });
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same for errors, please use the error handling middleware

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Server/index.js Outdated
@@ -284,6 +295,7 @@ const startApp = async () => {
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter());
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use("/api/v1/notifications", notificationRoutes.getRouter()); // Add this line
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please make sure to secure this route

}

initializeRoutes() {
this.router.post('/trigger', this.notificationController.triggerNotification.bind(this.notificationController));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we binding a context here?

Copy link
Member Author

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 for it here. I got rid of it thanks!

Copy link

@coderabbitai coderabbitai bot left a 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/service/notificationService.js (1)

125-133: ⚠️ Potential issue

Yo, this loop's got a missing brace, just like the bot mentioned!

The for loop is missing its closing brace, which would cause a syntax error.

  for (const notification of notifications) {
    if (notification.type === "email") {
      this.sendEmail(networkResponse, notification.address);
    } else if (["discord", "slack", "telegram"].includes(notification.type)) {
      this.sendWebhookNotification(networkResponse, notification.address, notification.type);
    }
-   // Handle other types of notifications here
  }
+ }
🧹 Nitpick comments (2)
Server/service/notificationService.js (2)

2-2: Yo dawg, let's move this URL to a config file!

The base URL should be moved to a centralized configuration file for easier maintenance and consistency.

-const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot";

Move this to a centralized config file, like:

// config/api.js
export const API_URLS = {
  TELEGRAM: "https://api.telegram.org/bot",
  // Add other API URLs here
};

21-34: Let's add some type definitions to make this spaghetti cleaner!

The method looks good but could benefit from JSDoc type definitions for better maintainability and IDE support.

Add JSDoc types like this:

/**
 * Formats notification message based on platform
 * @param {Object} monitor - The monitor object
 * @param {string} monitor.name - Monitor name
 * @param {string} monitor.url - Monitor URL
 * @param {boolean} status - Monitor status
 * @param {'telegram'|'slack'|'discord'} platform - Platform type
 * @param {string} [chatId] - Chat ID for Telegram
 * @returns {Object|null} Formatted message object
 */
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f46b076 and ef41712.

📒 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)

13-19: Clean constructor changes, my dude! 🍝

Good job injecting the networkService dependency. This aligns with the single responsibility principle and makes testing easier.


36-75: Mom's spaghetti level implementation! 🔥

Solid implementation with:

  • Proper error handling and logging
  • Clean separation of concerns using networkService
  • Good parameter validation

Copy link

@coderabbitai coderabbitai bot left a 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 (3)
Server/service/notificationService.js (3)

2-2: Yo dawg, let's move this URL to a central config!

The base URL should be moved to a central configuration file for easier maintenance and consistency.


21-34: Drop some sweet JSDoc beats on this function!

Add JSDoc documentation to specify parameter types and return values. This will help maintain the code and catch potential issues early.

+/**
+ * Formats notification message based on platform requirements
+ * @param {Object} monitor - The monitor object containing name and URL
+ * @param {boolean} status - The current status of the monitor
+ * @param {'telegram'|'slack'|'discord'} platform - The notification platform
+ * @param {string} [chatId] - The chat ID for Telegram notifications
+ * @returns {Object|null} Platform-specific message object or null if platform unsupported
+ */
 formatNotificationMessage(monitor, status, platform, chatId) {

36-75: Let's make this flow smoother than mom's spaghetti!

The implementation is solid but could be more elegant with early returns.

 async sendWebhookNotification(networkResponse, config) {
   const { monitor, status } = networkResponse;
   const { type, webhookUrl, botToken, chatId } = config;
-  let url = webhookUrl;
+  const message = this.formatNotificationMessage(monitor, status, type, chatId);
+  if (message === null) {
+    this.logger.warn({
+      message: `Unsupported webhook type: ${type}`,
+      service: this.SERVICE_NAME,
+      method: 'sendWebhookNotification',
+      type
+    });
+    return false;
+  }
 
-  const message = this.formatNotificationMessage(monitor, status, type, chatId);
-  if (message === null) {
-    this.logger.warn({
-      message: `Unsupported webhook type: ${type}`,
-      service: this.SERVICE_NAME,
-      method: 'sendWebhookNotification',
-      type
-    });
-    return false;
-  }
+  let url = webhookUrl;
+  if (type === 'telegram') {
+    if (!botToken || !chatId) return false;
+    url = `${TELEGRAM_API_BASE_URL}${botToken}/sendMessage`;
+  }
-  if (type === 'telegram') {
-    if (!botToken || !chatId) {
-      return false;
-    }
-    url = `${TELEGRAM_API_BASE_URL}${botToken}/sendMessage`;
-  }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef41712 and fa50706.

📒 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 (1)
Server/service/notificationService.js (1)

13-18: Straight fire! 🔥 NetworkService integration looks clean!

The addition of networkService follows the established pattern and properly separates network operations.

@@ -288,6 +299,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());

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix AI about 22 hours ago

To fix the problem, we need to introduce rate limiting to the routes that use the verifyJWT middleware. The best way to do this is by using the express-rate-limit package, which allows us to easily set up rate limiting for specific routes.

  1. Install the express-rate-limit package.
  2. Import the express-rate-limit package in the Server/index.js file.
  3. Set up a rate limiter with appropriate settings (e.g., maximum of 100 requests per 15 minutes).
  4. Apply the rate limiter to the routes that use the verifyJWT middleware.
Suggested changeset 2
Server/index.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Server/index.js b/Server/index.js
--- a/Server/index.js
+++ b/Server/index.js
@@ -3,3 +3,3 @@
 import swaggerUi from "swagger-ui-express";
-
+import rateLimit from "express-rate-limit";
 import express from "express";
@@ -44,2 +44,8 @@
 
+// set up rate limiter: maximum of 100 requests per 15 minutes
+const limiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 requests per windowMs
+});
+
 //JobQueue service and dependencies
@@ -316,11 +322,11 @@
 	app.use("/api/v1/auth", authRoutes.getRouter());
-	app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
-	app.use("/api/v1/invite", inviteRoutes.getRouter());
-	app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
-	app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
-	app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
-	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());
+	app.use("/api/v1/settings", limiter, verifyJWT, settingsRoutes.getRouter());
+	app.use("/api/v1/invite", limiter, inviteRoutes.getRouter());
+	app.use("/api/v1/monitors", limiter, verifyJWT, monitorRoutes.getRouter());
+	app.use("/api/v1/checks", limiter, verifyJWT, checkRoutes.getRouter());
+	app.use("/api/v1/maintenance-window", limiter, verifyJWT, maintenanceWindowRoutes.getRouter());
+	app.use("/api/v1/queue", limiter, verifyJWT, queueRoutes.getRouter());
+	app.use("/api/v1/distributed-uptime", limiter, distributedUptimeRoutes.getRouter());
+	app.use("/api/v1/status-page", limiter, statusPageRoutes.getRouter());
+	app.use("/api/v1/notifications", limiter, verifyJWT, notificationRoutes.getRouter());
 	app.use(handleErrors);
EOF
@@ -3,3 +3,3 @@
import swaggerUi from "swagger-ui-express";

import rateLimit from "express-rate-limit";
import express from "express";
@@ -44,2 +44,8 @@

// set up rate limiter: maximum of 100 requests per 15 minutes
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});

//JobQueue service and dependencies
@@ -316,11 +322,11 @@
app.use("/api/v1/auth", authRoutes.getRouter());
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
app.use("/api/v1/invite", inviteRoutes.getRouter());
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
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());
app.use("/api/v1/settings", limiter, verifyJWT, settingsRoutes.getRouter());
app.use("/api/v1/invite", limiter, inviteRoutes.getRouter());
app.use("/api/v1/monitors", limiter, verifyJWT, monitorRoutes.getRouter());
app.use("/api/v1/checks", limiter, verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", limiter, verifyJWT, maintenanceWindowRoutes.getRouter());
app.use("/api/v1/queue", limiter, verifyJWT, queueRoutes.getRouter());
app.use("/api/v1/distributed-uptime", limiter, distributedUptimeRoutes.getRouter());
app.use("/api/v1/status-page", limiter, statusPageRoutes.getRouter());
app.use("/api/v1/notifications", limiter, verifyJWT, notificationRoutes.getRouter());
app.use(handleErrors);
Server/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Server/package.json b/Server/package.json
--- a/Server/package.json
+++ b/Server/package.json
@@ -40,3 +40,4 @@
 		"swagger-ui-express": "5.0.1",
-		"winston": "^3.13.0"
+		"winston": "^3.13.0",
+		"express-rate-limit": "^7.5.0"
 	},
EOF
@@ -40,3 +40,4 @@
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
"winston": "^3.13.0",
"express-rate-limit": "^7.5.0"
},
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 7.5.0 None
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
Server/routes/notificationRoute.js Fixed Show fixed Hide fixed
Copy link

@coderabbitai coderabbitai bot left a 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/index.js (1)

302-302: ⚠️ Potential issue

Same rate limiting concern as before, fam!

The notification routes need rate limiting protection to prevent abuse.

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 302-302: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

🧹 Nitpick comments (1)
Server/routes/notificationRoute.js (1)

12-12: That binding though... let's make it cleaner!

Instead of binding the context in the route definition, consider using arrow functions or binding in the constructor.

Here's a cleaner approach using an arrow function:

-        this.router.post('/trigger', verifyJWT, this.notificationController.triggerNotification.bind(this.notificationController));
+        this.router.post('/trigger', verifyJWT, (req, res, next) => this.notificationController.triggerNotification(req, res, next));

Or bind in the constructor:

     constructor(notificationController) {
         this.notificationController = notificationController;
+        this.triggerNotification = this.notificationController.triggerNotification.bind(this.notificationController);
         this.router = express.Router();
         this.initializeRoutes();
     }

     initializeRoutes() {
-        this.router.post('/trigger', verifyJWT, this.notificationController.triggerNotification.bind(this.notificationController));
+        this.router.post('/trigger', verifyJWT, this.triggerNotification);
     }
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 12-12: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa50706 and 67da574.

📒 Files selected for processing (2)
  • Server/index.js (5 hunks)
  • Server/routes/notificationRoute.js (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
Server/index.js

[failure] 302-302: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

Server/routes/notificationRoute.js

[failure] 12-12: 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/index.js (1)

181-181: Yo, that's a clean service initialization!

The NotificationService initialization follows the established pattern and properly includes all required dependencies.

}

initializeRoutes() {
this.router.post('/trigger', verifyJWT, this.notificationController.triggerNotification.bind(this.notificationController));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo dawg, we need to protect this endpoint from abuse!

The /trigger endpoint needs rate limiting to prevent abuse. Even with JWT verification, authenticated users could still spam notifications.

Add rate limiting middleware before the route handler:

-        this.router.post('/trigger', verifyJWT, this.notificationController.triggerNotification.bind(this.notificationController));
+        this.router.post('/trigger', 
+            verifyJWT,
+            rateLimiter({
+                windowMs: 60 * 1000, // 1 minute
+                max: 10 // limit each IP to 10 requests per windowMs
+            }),
+            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.

Suggested change
this.router.post('/trigger', verifyJWT, this.notificationController.triggerNotification.bind(this.notificationController));
this.router.post('/trigger',
verifyJWT,
rateLimiter({
windowMs: 60 * 1000, // 1 minute
max: 10 // limit each IP to 10 requests per windowMs
}),
this.notificationController.triggerNotification.bind(this.notificationController)
);
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 12-12: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

initializeRoutes() {
this.router.post(
'/trigger',
verifyJWT,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix AI about 19 hours ago

To fix the problem, we need to introduce a rate-limiting middleware to the Express application. The express-rate-limit package is a well-known library for this purpose. We will set up a rate limiter that restricts the number of requests a client can make to the /trigger endpoint within a specified time window. This will help prevent denial-of-service attacks by limiting the rate at which requests are accepted.

  1. Install the express-rate-limit package.
  2. Import the express-rate-limit package in the Server/routes/notificationRoute.js file.
  3. Set up a rate limiter with a reasonable configuration (e.g., 100 requests per 15 minutes).
  4. Apply the rate limiter to the /trigger endpoint.
Suggested changeset 2
Server/routes/notificationRoute.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Server/routes/notificationRoute.js b/Server/routes/notificationRoute.js
--- a/Server/routes/notificationRoute.js
+++ b/Server/routes/notificationRoute.js
@@ -1,2 +1,3 @@
 import express from 'express';
+import RateLimit from 'express-rate-limit';
 import { verifyJWT } from '../middleware/verifyJWT.js';
@@ -8,2 +9,6 @@
         this.router = express.Router();
+        this.limiter = RateLimit({
+            windowMs: 15 * 60 * 1000, // 15 minutes
+            max: 100, // max 100 requests per windowMs
+        });
         this.initializeRoutes();
@@ -37,2 +42,3 @@
             verifyJWT,
+            this.limiter,
             this.validateRequest(triggerNotificationBodyValidation),
EOF
@@ -1,2 +1,3 @@
import express from 'express';
import RateLimit from 'express-rate-limit';
import { verifyJWT } from '../middleware/verifyJWT.js';
@@ -8,2 +9,6 @@
this.router = express.Router();
this.limiter = RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // max 100 requests per windowMs
});
this.initializeRoutes();
@@ -37,2 +42,3 @@
verifyJWT,
this.limiter,
this.validateRequest(triggerNotificationBodyValidation),
Server/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Server/package.json b/Server/package.json
--- a/Server/package.json
+++ b/Server/package.json
@@ -40,3 +40,4 @@
 		"swagger-ui-express": "5.0.1",
-		"winston": "^3.13.0"
+		"winston": "^3.13.0",
+		"express-rate-limit": "^7.5.0"
 	},
EOF
@@ -40,3 +40,4 @@
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
"winston": "^3.13.0",
"express-rate-limit": "^7.5.0"
},
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 7.5.0 None
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
Copy link

@coderabbitai coderabbitai bot left a 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 (4)
Server/controllers/notificationController.js (1)

24-24: ⚠️ Potential issue

Use response handling middleware.

The response handling should go through the middleware to maintain consistent response format across the application.

Replace direct response handling with middleware:

-            res.json({ success: true, msg: "Notification sent successfully" });
+            next({ success: true, msg: "Notification sent successfully" });

-            res.status(500).json({ success: false, msg: "Failed to send notification" });
+            next({ success: false, msg: "Failed to send notification", status: 500 });

Also applies to: 32-32

Server/routes/notificationRoute.js (1)

35-40: ⚠️ Potential issue

Add rate limiting and remove unnecessary binding.

Yo, we need to protect this endpoint from abuse and clean up the code!

Apply these changes:

         this.router.post(
             '/trigger', 
             verifyJWT,
+            rateLimiter({
+                windowMs: 60 * 1000, // 1 minute
+                max: 10 // limit each IP to 10 requests per windowMs
+            }),
             this.validateRequest(triggerNotificationBodyValidation),
-            this.notificationController.triggerNotification.bind(this.notificationController)
+            this.notificationController.triggerNotification
         );
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 37-37: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

Server/index.js (1)

325-325: ⚠️ Potential issue

Add rate limiting to the notifications endpoint.

Yo, we need to protect this global route from abuse too!

Apply rate limiting to the notifications endpoint:

-    app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
+    app.use("/api/v1/notifications", 
+        verifyJWT,
+        rateLimiter({
+            windowMs: 60 * 1000, // 1 minute
+            max: 10 // limit each IP to 10 requests per windowMs
+        }),
+        notificationRoutes.getRouter()
+    );
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 325-325: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

Server/service/networkService.js (1)

421-464: ⚠️ Potential issue

Yo dawg, we need to validate these inputs before they wreck our system! 🎤

The method needs essential validation and reliability improvements:

  1. Input validation for required parameters
  2. URL validation to ensure HTTPS
  3. Rate limiting to prevent abuse
  4. Retry mechanism for failed requests

The previous review already suggested these improvements. Let's apply them with some additional swagger:

 async requestWebhook(platform, url, message) {
+    // Validate inputs or throw errors like mom's spaghetti
+    if (!platform || !url || !message) {
+      throw new Error('Platform, URL, and message are required parameters');
+    }
+    if (!url.startsWith('https://')) {
+      throw new Error('Webhook URL must use HTTPS protocol');
+    }
+
+    // Helper function for retries
+    const withRetry = async (operation, maxRetries = 3, delay = 1000) => {
+      let lastError;
+      for (let attempt = 1; attempt <= maxRetries; attempt++) {
+        try {
+          return await operation();
+        } catch (error) {
+          lastError = error;
+          if (attempt < maxRetries) {
+            await new Promise(resolve => setTimeout(resolve, delay * attempt));
+          }
+        }
+      }
+      throw lastError;
+    };

     try {
-      const { response, responseTime, error } = await this.timeRequest(() =>
+      const { response, responseTime, error } = await this.timeRequest(() => withRetry(() =>
         this.axios.post(url, message, {
           headers: {
-            'Content-Type': 'application/json'
+            'Content-Type': 'application/json',
+            'User-Agent': 'Checkmate-Bot/1.0'
           },
+          timeout: 5000,
+          maxContentLength: 1024 * 1024, // 1MB limit
+          maxBodyLength: 1024 * 1024
         })
-      );
+      ));
🧹 Nitpick comments (1)
Server/validation/joi.js (1)

518-529: Improve conditional validation syntax and URL validation.

The validation could be more robust with these improvements:

Apply these changes:

         config: joi.alternatives()
-            .conditional('type', {
-                is: 'webhook',
-                then: joi.alternatives().try(
+            .when('type', {
+                is: 'webhook',
+                then: joi.alternatives().try(
                     telegramWebhookConfigValidation,
                     discordWebhookConfigValidation,
                     slackWebhookConfigValidation
                 ).required().messages({
                     'any.required': 'Webhook configuration is required'
                 })
             })

Also, add stricter URL validation for webhook URLs:

         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'
-        })
+        }).custom((value, helpers) => {
+            if (!value.startsWith('https://discord.com/api/webhooks/')) {
+                return helpers.message('Invalid Discord webhook URL format');
+            }
+            return value;
+        })
🧰 Tools
🪛 Biome (1.9.4)

[error] 521-521: Do not add then to an object.

(lint/suspicious/noThenProperty)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67da574 and 012483d.

📒 Files selected for processing (5)
  • Server/controllers/notificationController.js (1 hunks)
  • Server/index.js (5 hunks)
  • Server/routes/notificationRoute.js (1 hunks)
  • Server/service/networkService.js (1 hunks)
  • Server/validation/joi.js (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
Server/validation/joi.js

[error] 521-521: Do not add then to an object.

(lint/suspicious/noThenProperty)

🪛 GitHub Check: CodeQL
Server/routes/notificationRoute.js

[failure] 37-37: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

Server/index.js

[failure] 325-325: 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)

Copy link

@coderabbitai coderabbitai bot left a 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

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8007daa and 70ffe1b.

📒 Files selected for processing (1)
  • Server/controllers/notificationController.js (1 hunks)
🔇 Additional comments (2)
Server/controllers/notificationController.js (2)

2-5: Yo, this class structure is fire! 🔥

Clean dependency injection pattern with the notification service. That's the way we like it!


11-16: Mom's spaghetti alert! 🍝 Still got that hardcoded test data!

This issue was previously flagged but still needs addressing. We gotta keep it real with actual monitor data, not test values.

Copy link

@coderabbitai coderabbitai bot left a 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/routes/notificationRoute.js (1)

34-41: ⚠️ Potential issue

Yo, we still need that rate limiting shield! 🛡️

The /trigger endpoint needs protection from spam attacks, even with JWT verification.

Add rate limiting middleware:

 initializeRoutes() {
     this.router.post(
         '/trigger', 
         verifyJWT,
+        rateLimiter({
+            windowMs: 60 * 1000,  // 1 minute window
+            max: 10,  // 10 requests per window
+            message: { 
+                success: false, 
+                error: { 
+                    message: 'Too many requests, please try again later',
+                    code: 'RATE_LIMIT_EXCEEDED'
+                }
+            }
+        }),
         this.validateRequest(triggerNotificationBodyValidation),
         this.notificationController.triggerNotification
     );
 }

Don't forget to add the import:

import rateLimit from 'express-rate-limit';
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 37-37: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

🧹 Nitpick comments (1)
Server/routes/notificationRoute.js (1)

12-32: Yo dawg, let's beef up this validation! 💪

The validation is solid, but we should add some extra security:

 validateRequest(schema) {
     return (req, res, next) => {
         const { error } = schema.validate(req.body, {
             abortEarly: false,
-            stripUnknown: true
+            stripUnknown: true,
+            convert: false,  // Prevent type coercion
+            presence: 'required'  // Ensure all required fields are present
         });

         if (error) {
             const errorMessage = error.details
                 .map(detail => detail.message)
                 .join(', ');
                 
             return res.status(400).json({
                 success: false,
-                msg: errorMessage
+                error: {
+                    message: errorMessage,
+                    code: 'VALIDATION_ERROR'
+                }
             });
         }

         next();
     };
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70ffe1b and 12d9f15.

📒 Files selected for processing (2)
  • Server/controllers/notificationController.js (1 hunks)
  • Server/routes/notificationRoute.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Server/controllers/notificationController.js
🧰 Additional context used
🪛 GitHub Check: CodeQL
Server/routes/notificationRoute.js

[failure] 37-37: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

🔇 Additional comments (1)
Server/routes/notificationRoute.js (1)

1-10: Yo, this class structure is fire! 🔥

Clean dependency injection and solid class organization. The constructor properly initializes the router and dependencies.

Comment on lines +37 to +39
verifyJWT,
this.validateRequest(triggerNotificationBodyValidation),
this.notificationController.triggerNotification
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Yo, we gotta validate those webhook URLs! 🎯

Since we're handling Discord webhook URLs, we need to ensure they're legit.

Add URL validation to the Joi schema in triggerNotificationBodyValidation:

const webhookUrlPattern = /^https:\/\/(?:ptb\.|canary\.)?discord\.com\/api\/webhooks\/\d+\/[\w-]+$/;

const triggerNotificationBodyValidation = Joi.object({
  // ... existing validation
  config: Joi.object({
    url: Joi.string()
      .pattern(webhookUrlPattern)
      .message('Invalid Discord webhook URL format')
      .required()
  }).when('type', {
    is: 'webhook',
    then: Joi.required()
  })
});
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 37-37: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 13, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 13, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 13, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 13, 2025
@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Feb 13, 2025
Copy link
Collaborator

@ajhollid ajhollid left a 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;
Copy link
Collaborator

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

webhookUrl: String,
botToken: String,
chatId: String
},
Copy link
Collaborator

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.


next();
};
}
Copy link
Collaborator

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.

'Content-Type': 'application/json'
}
})
);
Copy link
Collaborator

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.

@@ -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.
Copy link
Collaborator

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}`,
Copy link
Collaborator

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

if (platform === 'discord') {
return { content: messageText };
}
return null;
Copy link
Collaborator

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.

}

formatNotificationMessage(monitor, status, platform, chatId) {
const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Localization service neeed here

formatNotificationMessage(monitor, status, platform, chatId) {
const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`;

if (platform === 'telegram') {
Copy link
Collaborator

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.

const { monitor, status } = networkResponse;
const { type, webhookUrl, botToken, chatId } = config;
let url = webhookUrl;

Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants