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

Adds firebase configuration #39

Merged
merged 17 commits into from
Feb 12, 2025
Merged

Adds firebase configuration #39

merged 17 commits into from
Feb 12, 2025

Conversation

PanchoBubble
Copy link
Collaborator

@PanchoBubble PanchoBubble commented Feb 4, 2025

https://www.loom.com/share/00f815219f1b446aa25fa3be9a57d960

Summary by CodeRabbit

  • New Features
    • Integrated Firebase push notifications, providing an additional method to send notifications.
    • Added a new route for handling Firebase-related requests.
    • Introduced new middleware for validating requests to the Firebase endpoint.
    • Implemented functionality for sending push notifications using the Firebase Admin SDK.
    • Added support for additional parameters in token registration and user token retrieval.
    • Added a new entry to the .prettierignore file to specify ignored paths for formatting.
    • Created a new .prettierrc configuration file for code formatting options.
  • Refactor
    • Enhanced asynchronous processing and refined error handling for improved reliability.
    • Streamlined internal logging and parameter management.
  • Chores
    • Updated configurations and dependencies to support the new features and improvements.
    • Added pnpm-lock.yaml to .gitignore.
    • Added new columns to the push_tokens table in the database schema.

@PanchoBubble PanchoBubble marked this pull request as ready for review February 5, 2025 15:30
@PanchoBubble PanchoBubble self-assigned this Feb 5, 2025
Copy link

coderabbitai bot commented Feb 10, 2025

Walkthrough

The changes update various project files. The .gitignore now ignores the pnpm-lock.yaml file. In the Express middleware and several route handlers, unused parameters were renamed (using an underscore prefix) and redundant debug imports were removed. Multiple functions in lib/database.js were refactored to use async/await. A new module has been added to handle Firebase push notifications, and a matching route for sending these notifications was introduced. Additionally, new dependencies for Firebase and Prettier were added to the package configuration.

Changes

File(s) Change Summary
.gitignore Added entry: pnpm-lock.yaml
app.js Added firebaseRouter and route for /send-firebase
lib/database.js Converted several functions to asynchronous (async/await) and updated query result handling
lib/push_notifications_firebase.js New module implementing Firebase push notifications via the Firebase Admin SDK
lib/reminders.js Removed debug logging (deletion of debug import)
package.json Added dependencies: "firebase-admin": "^13.0.2" and "prettier": "^3.5.0"
routes/admin.js, routes/cancel_reminders.js, routes/health.js, routes/index.js, routes/register.js, routes/send.js Renamed unused parameters (e.g., req to _req or next to _next), removed unused imports, and in routes/send.js added a new route for sending Firebase push notifications
migrations/0003-app-id-data.sql Added columns: user_id VARCHAR(255) and app_id VARCHAR(255) to push_tokens table
routes/sendFirebase.js New router with check_signature middleware and sendFirebase POST handler

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant R as sendFirebase Route
    participant F as Firebase Push Module
    participant FA as Firebase Admin SDK

    C->>R: POST /send-firebase with payload
    R->>F: Call sendPushNotification()
    F->>FA: Send notification message
    FA-->>F: Return response or error
    F-->>R: Return result
    R-->>C: JSON response with status
Loading

Poem

I'm a happy bunny hopping through the code,
Async adventures lighten my heavy load.
Firebase messages flutter like leaves in the breeze,
Unused parameters renamed with graceful ease.
With each new change, my circuits sing in delight,
A rabbit's rhythm in code, from morning to night! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • 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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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

🔭 Outside diff range comments (1)
routes/admin.js (1)

7-7:

🧩 Analysis chain

Verify the removal of reminders functionality.

The removal of the reminders route and function might impact other parts of the codebase that depend on this functionality.

Run the following script to check for any remaining references to the removed functionality:

Also applies to: 25-31


🏁 Script executed:

#!/bin/bash
# Description: Check for any remaining references to list_reminders

# Test: Search for references to list_reminders. Expect: No references except in comments.
rg -A 5 'list_reminders' --type js

Length of output: 1519


Impact Detected: Reminders Functionality Still in Use

  • The list_reminders function is still implemented and exported in lib/database.js.
  • It’s actively referenced in lib/reminders.js (e.g., with await db.list_reminders(new Date())).
  • The commented-out code in routes/admin.js only affects the admin endpoint and does not remove or disable the underlying reminders functionality.

Please confirm if the intent was to disable reminders entirely. If so, additional cleanup in lib/database.js and in any modules (like lib/reminders.js) invoking this function is required.

🧹 Nitpick comments (4)
routes/health.js (1)

32-56: Refactor to use await consistently.

The function is marked as async but still uses promise chaining. Consider refactoring to use await consistently for better readability and error handling.

Here's a suggested refactor:

 async function healthCheck(_req, res, _next) {
-    return db.healthCheck().then(dbResult => {
+    try {
+        const dbResult = await db.healthCheck();
         if (!dbResult) {
             console.error("Missing DB result");
             return res.send(DB_UNREACHABLE);
         }
 
-        canReadFile(`certs/${process.env.APPLE_CERT_NAME}`).then((success) => {
-            if (success) {
-                return res.send(OK);
-            } else {
-                console.error("Failed to read cert");
-                return res.send(CERT_UNREADABLE);
-            }
-        }).catch((error) => {
+        try {
+            const success = await canReadFile(`certs/${process.env.APPLE_CERT_NAME}`);
+            if (success) {
+                return res.send(OK);
+            }
             console.error("Failed to read cert");
-            debug(error);
             return res.send(CERT_UNREADABLE);
-        });
-    }).catch(err => {
+        } catch (error) {
+            console.error("Failed to read cert");
+            debug(error);
+            return res.send(CERT_UNREADABLE);
+        }
+    } catch (err) {
         console.error("DB connection error");
         debug(err);
         return res.status(500).send(DB_UNREACHABLE);
-    });
+    }
 }
lib/database.js (2)

30-46: Refactor to use await consistently.

The function is marked as async but still uses promise chaining. Consider refactoring to use await and try/catch for better readability and error handling.

Here's a suggested refactor:

 async function register_token(pub_key, token, platform, sandbox) {
     const q = `
         INSERT INTO push_tokens (pub_key, token, platform, sandbox, updated_at)
         VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP) 
         ON CONFLICT ON CONSTRAINT index_pub_key_token
         DO UPDATE SET platform = $3, sandbox = $4, updated_at = CURRENT_TIMESTAMP
      `;
     debug(`Setting token for ${pub_key}`);
-    return pool.query(q, [pub_key.toLowerCase(), token, platform, sandbox]).then(res => {
+    try {
+        const res = await pool.query(q, [pub_key.toLowerCase(), token, platform, sandbox]);
         const success = res.rowCount > 0;
         if (!success) {
             throw "pub_key not added/updated.";
         }
         return true;
-    });
+    } catch (error) {
+        throw error;
+    }
 }

52-143: Apply consistent async/await pattern across all functions.

Multiple functions are marked as async but still use promise chaining. Consider applying the same refactoring pattern consistently across all functions for better readability and error handling.

Here's the pattern to follow for each function:

async function functionName(...args) {
    try {
        const res = await pool.query(q, [...args]);
        // Handle success case
        return result;
    } catch (error) {
        // Handle error case
        throw error;
    }
}
lib/push_notifications_firebase.js (1)

25-69: Enhance payload validation and error handling.

While the function handles basic validation, consider these improvements:

  1. Validate payload structure more thoroughly
  2. Add type checking for critical fields
  3. Structure debug logs for better traceability
 const sendPushNotification = async (deviceToken, payload) => {
+    // Validate payload structure
+    if (!payload || typeof payload !== 'object') {
+        throw new Error('Invalid payload format');
+    }
+
     const { title,
         pushType = "alert",
         sound = "default",
         body,
         expiry,
         topic,
     } = payload;
+
+    // Validate required fields
+    if (!title || !body) {
+        throw new Error('Title and body are required');
+    }

     // ... rest of the function
     try {
         const response = await admin.messaging().send(message);
-        debug(`Notification sent successfully: ${response}`);
+        debug({
+            event: 'notification_sent',
+            response,
+            deviceToken: deviceToken || 'N/A',
+            topic: topic || 'N/A'
+        });
         return response;
     } catch (error) {
-        debug(`Error sending notification: ${error.message}`);
+        debug({
+            event: 'notification_error',
+            error: error.message,
+            deviceToken: deviceToken || 'N/A',
+            topic: topic || 'N/A'
+        });
         throw error;
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bf35a7 and db348e9.

📒 Files selected for processing (12)
  • .gitignore (1 hunks)
  • app.js (1 hunks)
  • lib/database.js (6 hunks)
  • lib/push_notifications_firebase.js (1 hunks)
  • lib/reminders.js (0 hunks)
  • package.json (1 hunks)
  • routes/admin.js (1 hunks)
  • routes/cancel_reminders.js (0 hunks)
  • routes/health.js (1 hunks)
  • routes/index.js (1 hunks)
  • routes/register.js (0 hunks)
  • routes/send.js (4 hunks)
💤 Files with no reviewable changes (3)
  • routes/register.js
  • routes/cancel_reminders.js
  • lib/reminders.js
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • routes/index.js
  • app.js
🔇 Additional comments (4)
routes/admin.js (1)

19-23: LGTM!

Good practice to use _ prefix for unused parameters.

lib/database.js (1)

16-23: LGTM!

Good use of async/await pattern.

lib/push_notifications_firebase.js (1)

5-16: Validate environment variables before initialization.

The Firebase configuration relies on multiple environment variables but doesn't validate their presence. Missing variables could cause runtime errors.

Add validation before initialization:

 const firebaseConfig = {
+    // Validate required environment variables
+    if (!process.env.FIREBASE_PROJECT_ID || !process.env.FIREBASE_PRIVATE_KEY) {
+        throw new Error('Missing required Firebase configuration environment variables');
+    }
     type: "service_account",
     auth_uri: "https://accounts.google.com/o/oauth2/auth",
package.json (1)

13-13: Verify Firebase Admin SDK version compatibility and security.

The Firebase Admin SDK version should be verified for compatibility with the Node.js version in use and checked for any security advisories.

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Check for compatible Node.js versions and security advisories
npm view [email protected] engines.node
npm audit [email protected]

Length of output: 433


Verify Firebase Admin SDK Dependency Compatibility and Security

  • Node.js Compatibility: The output confirms that firebase-admin v13.0.2 requires Node.js version >=18. Please ensure that the Node.js version used in your project meets this requirement.
  • Security Audit: The npm audit was not executed successfully due to the absence of a lockfile. To perform a proper security audit of the dependency, generate a lockfile by running npm i --package-lock-only and then re-run npm audit.

routes/send.js Outdated Show resolved Hide resolved
routes/send.js Outdated Show resolved Hide resolved
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

🧹 Nitpick comments (1)
migrations/0003-app-id-data.sql (1)

1-3: Consider indexing new columns if querying by user_id or app_id.

If the application frequently searches or filters by these columns, adding indexes may significantly boost query performance, especially for large datasets.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8bb726 and aa13a1e.

📒 Files selected for processing (8)
  • .prettierignore (1 hunks)
  • .prettierrc (1 hunks)
  • app.js (2 hunks)
  • lib/database.js (2 hunks)
  • migrations/0003-app-id-data.sql (1 hunks)
  • package.json (1 hunks)
  • routes/send.js (6 hunks)
  • routes/sendFirebase.js (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • .prettierignore
  • .prettierrc
🚧 Files skipped from review as they are similar to previous changes (3)
  • app.js
  • routes/send.js
  • lib/database.js
🔇 Additional comments (4)
routes/sendFirebase.js (3)

19-20: Ensure APP_API_KEY is defined or imported.

The code references APP_API_KEY, but it is not declared in this file. Confirm that it’s either set as an environment variable or imported from a config file.

Please verify or add the definition. For example, if using environment variables:

+ const { APP_API_KEY } = process.env;
...
const msg = `${APP_API_KEY}${from_pub_key}${to_pub_key}`;

60-62: Verify EXPIRES_AFTER_HOURS is defined.

EXPIRES_AFTER_HOURS is not declared in this file. Ensure its origin is properly set (e.g., environment variable or config file).


82-82: Confirm REMINDER_PUSH_NOTIFICATIONS_ENABLED usage.

REMINDER_PUSH_NOTIFICATIONS_ENABLED is referenced but not declared. Verify it's properly imported or defined, otherwise reminders won't function as intended.

package.json (1)

13-13: Dependencies look good; verify no version conflicts.

Including "firebase-admin" and "prettier" is aligned with the new features and formatting guidelines. Verify that these versions do not conflict with existing dependencies or require additional configuration.

Also applies to: 20-21

routes/sendFirebase.js Outdated Show resolved Hide resolved
routes/sendFirebase.js Outdated Show resolved Hide resolved
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

🧹 Nitpick comments (4)
routes/sendFirebase.js (2)

23-23: Use debug module instead of console.log.

Replace console.log statements with the imported debug module for consistent logging.

- console.log('msg', msg);
+ debug('msg %s', msg);

- console.log('Valid with new check');
+ debug('Valid with new check');

Also applies to: 27-27


90-90: Simplify success response.

The double negation !!success is unnecessary since success is already a boolean.

- return res.json({ success: !!success });
+ return res.json({ success });
lib/database.js (2)

56-56: Improve error message in register_token.

The current error message doesn't provide enough context about why the operation failed.

- throw 'pub_key not added/updated.';
+ throw `Failed to register token for pub_key ${pub_key}. No rows were affected.`;

67-77: Use consistent async/await pattern.

The functions are marked as async but still use .then(). Consider using consistent async/await pattern throughout for better readability.

Example for delete_token:

 async function delete_token(pub_key, token) {
     const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
     debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
-    return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
-        const success = res.rowCount > 0;
-        if (!success) {
-            throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
-        }
-        return true;
-    });
+    const res = await pool.query(q, [pub_key.toLowerCase(), token]);
+    const success = res.rowCount > 0;
+    if (!success) {
+        throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
+    }
+    return true;
 }

Also applies to: 84-98, 105-114, 121-129, 136-144, 146-151, 153-158

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 814b786 and e9c444d.

📒 Files selected for processing (6)
  • lib/database.js (6 hunks)
  • lib/push_notifications_firebase.js (1 hunks)
  • lib/reminders.js (7 hunks)
  • routes/register.js (3 hunks)
  • routes/send.js (7 hunks)
  • routes/sendFirebase.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/reminders.js
  • routes/register.js
  • routes/send.js
  • lib/push_notifications_firebase.js
🔇 Additional comments (3)
routes/sendFirebase.js (2)

15-16: Include a leading slash in the route paths.

Express route parameters typically require a leading slash. Without it, the router might not map the request URL as expected.

- firebaseRouter.use('/', check_signature);
- firebaseRouter.post('/', sendFirebase);
+ firebaseRouter.use('/:to_pub_key', check_signature);
+ firebaseRouter.post('/:to_pub_key', sendFirebase);

43-43: Fix the check for empty token array.

The current condition negates tokenRows while expecting it to be an array. This won't correctly detect empty arrays.

- if (!tokenRows && Array.isArray(tokenRows) && tokenRows.length === 0) {
+ if (!tokenRows || !Array.isArray(tokenRows) || tokenRows.length === 0) {
lib/database.js (1)

22-24: Review the OR conditions in the SQL query.

Using OR conditions for all fields might return unexpected results. Consider if AND conditions would be more appropriate for your use case.

    const q = `SELECT token, sandbox
               from push_tokens
-              WHERE pub_key = $1 OR app_id = $2 OR user_id = $3`;
+              WHERE pub_key = $1 AND (app_id = $2 OR user_id = $3)`;

routes/sendFirebase.js Show resolved Hide resolved
routes/sendFirebase.js Outdated Show resolved Hide resolved
routes/sendFirebase.js Outdated Show resolved Hide resolved
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

♻️ Duplicate comments (4)
routes/sendFirebase.js (4)

45-47: ⚠️ Potential issue

Fix the check for empty token array.

The current condition negates tokenRows while expecting it to be an array, which won't correctly detect empty arrays.

Apply this diff to fix the logic:

-        if (!tokenRows && Array.isArray(tokenRows) && tokenRows.length === 0) {
+        if (!tokenRows || !Array.isArray(tokenRows) || tokenRows.length === 0) {

48-51: ⚠️ Potential issue

Return error response on token retrieval failure.

The error is logged but no response is sent, which could leave the request hanging.

Apply this diff to fix error handling:

    } catch (error) {
        console.error(`Failed to get device tokens for pub_key ${to_pub_key}`);
        console.error(error);
+       return res.status(500).json({ 
+           success: false, 
+           error: 'Failed to retrieve device tokens' 
+       });
    }

60-74: ⚠️ Potential issue

Fix success flag handling in notification loop.

The success flag is set but never reset between iterations. If any notification succeeds, the overall result will be success, even if subsequent notifications fail.

Apply this diff to fix the logic:

    try {
+       success = true;
        for (const { token, sandbox, pub_key } of tokenRows) {
            pubKey = pub_key;
            const service = sandbox ? sandbox_push_notifications : firebase_push_notifications;
            debug(`The send service is (sandbox=${sandbox})`);

            const sendResult = await service(token.trim(), payload);
            debug(`The sendResult of the notification service is ${sendResult}`);
-           success = true;
+           if (!sendResult) {
+               success = false;
+               break;
+           }
        }

85-91: ⚠️ Potential issue

Sanitize error response.

Sending raw error objects in responses could expose sensitive information.

Apply this diff to sanitize the error:

    if (!success) {
        console.error(error);
        return res.status(500).json({
            success: false,
-           error
+           error: 'Failed to send notification'
        });
    }
🧹 Nitpick comments (1)
lib/database.js (1)

67-158: Use consistent async/await pattern across all functions.

The database operations mix promise chains with async/await. Consider refactoring all functions to use async/await consistently for better readability and error handling.

Example refactor for delete_token:

async function delete_token(pub_key, token) {
    const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
    debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
-    return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
-        const success = res.rowCount > 0;
-        if (!success) {
-            throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
-        }
-        return true;
-    });
+    const res = await pool.query(q, [pub_key.toLowerCase(), token]);
+    if (res.rowCount === 0) {
+        throw new Error(`Failed to delete token ${token.substring(0, 4)}...`);
+    }
+    return true;
}

Apply similar refactoring to other functions using promise chains.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9c444d and 6a1d38c.

📒 Files selected for processing (3)
  • lib/database.js (6 hunks)
  • lib/push_notifications_firebase.js (1 hunks)
  • routes/sendFirebase.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/push_notifications_firebase.js
🔇 Additional comments (1)
routes/sendFirebase.js (1)

1-16: LGTM! Route configuration is correct.

The router is properly configured with root path ('/') since it's mounted at '/send-firebase' in app.js.

routes/sendFirebase.js Outdated Show resolved Hide resolved
lib/database.js Outdated Show resolved Hide resolved
lib/database.js Outdated
return pool.query(q, [pub_key.toLowerCase()]).then(res => {
return res.rows;
});
WHERE pub_key = $1 OR app_id = $2 OR user_id = $3`;
Copy link

@coderabbitai coderabbitai bot Feb 11, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

⚠️ Potential issue

🧩 Analysis chain

Revise the WHERE clause logic.

Using OR conditions for all identifiers could return unintended results. Consider using a more specific combination of identifiers based on your business logic.


🏁 Script executed:

#!/bin/bash
# Check if there's an index for this query
rg -A 5 'CREATE.*INDEX.*push_tokens'

Length of output: 218


Revise the WHERE Clause Logic in Token Retrieval

  • The SQL in lib/database.js uses:

    WHERE pub_key = $1 OR app_id = $2 OR user_id = $3
    

    Using OR across these identifiers may return rows that match on just one of these fields, possibly leading to unintended results.

  • The migration file (migrations/0001-initial.sql) shows an index for pub_key (and one for id) but no indexes for app_id or user_id. This discrepancy suggests that the query logic might not be aligned with the optimized database design or the intended business logic.

Please review and adjust the query to ensure it retrieves tokens as expected—potentially by revising the condition to better reflect the proper combination of identifiers (or by adding the necessary indexes if multiple identifiers are indeed required).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Intended behaviour, one app_id or user_id might be registered on multiple devices

Copy link

Choose a reason for hiding this comment

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

@PanchoBubble Understood, thank you for the clarification about the token retrieval logic.


✏️ Learnings added
Learnt from: PanchoBubble
PR: tari-labs/ios_aurora_push#39
File: lib/database.js:24-24
Timestamp: 2025-02-12T13:00:34.669Z
Learning: In the push_tokens table, the WHERE clause `pub_key = $1 OR app_id = $2 OR user_id = $3` is intentionally designed to allow multiple devices to be registered with the same app_id or user_id, returning all matching rows based on any of these identifiers.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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)
migrations/0003-app-id-data.sql (1)

1-3: Consider adding constraints and documentation.

The column additions look good, but consider these improvements:

  1. Add column comments to document their purpose
  2. Consider adding foreign key constraints if these IDs reference other tables
  3. Consider adding NOT NULL constraints with DEFAULT values if these columns should never be null

Example improvement:

 ALTER TABLE push_tokens
-ADD COLUMN IF NOT EXISTS user_id VARCHAR(255),
-ADD COLUMN IF NOT EXISTS app_id VARCHAR(255);
+ADD COLUMN IF NOT EXISTS user_id VARCHAR(255) NOT NULL DEFAULT '',
+ADD COLUMN IF NOT EXISTS app_id VARCHAR(255) NOT NULL DEFAULT '',
+COMMENT ON COLUMN push_tokens.user_id IS 'Unique identifier for the user',
+COMMENT ON COLUMN push_tokens.app_id IS 'Identifier for the Firebase application';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a1d38c and 8e9d067.

📒 Files selected for processing (3)
  • lib/database.js (6 hunks)
  • migrations/0003-app-id-data.sql (1 hunks)
  • routes/sendFirebase.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • routes/sendFirebase.js
  • lib/database.js
🔇 Additional comments (1)
migrations/0003-app-id-data.sql (1)

5-6: LGTM! Verify query patterns for optimal indexing.

The indexes are well-named and safely created. However, depending on your query patterns, you might benefit from a composite index if you frequently query using both columns together.

Let's check the query patterns in the codebase:

✅ Verification successful

🏁 Script executed:

#!/bin/bash
# Search for SQL queries using user_id and app_id columns
rg -i "where.*user_id.*app_id|where.*app_id.*user_id" --type sql
rg -i "select.*where.*user_id|select.*where.*app_id" --type js

Length of output: 131


Query Patterns Verification & Composite Index Consideration

The automated searches we ran did not yield any queries where both the user_id and app_id columns are used together. This suggests that, based on our current evidence, the existing single-column indexes appear appropriate. However, because dynamic queries or non-obvious composite conditions might exist, please manually verify any query-generation patterns in the application code to ensure no composite queries are overlooked.

To further assist, you can run this additional script to search for files that contain both keywords:


🏁 Script executed:

[run_scripts]
#!/bin/bash
# List all files that contain both "user_id" and "app_id"
rg -l "user_id" | xargs -r grep -li "app_id"

Length of output: 212


Let's further investigate whether any composite query conditions exist in the application code (especially in lib/database.js) that might benefit from a composite index. In addition to the earlier search, please run the following script to extract any context lines containing composite conditions (i.e. usage of both user_id and app_id in the same WHERE clause):

If this search produces no output, it would confirm that the current single-column indexes are appropriate. Otherwise, if composite conditions are found, you might consider adding a composite index.

Please run this additional check and review the output.


🏁 Script executed:

[run_scripts]
#!/bin/bash
echo "Searching for composite query conditions in lib/database.js..."
rg -C5 -i "where.*user_id.*and.*app_id" lib/database.js
rg -C5 -i "where.*app_id.*and.*user_id" lib/database.js

Length of output: 372


Query Patterns Verified – Single-Column Indexes Are Sufficient

The investigation into the codebase (including in lib/database.js) did not reveal any composite query conditions involving both user_id and app_id. Hence, the existing single-column indexes with IF NOT EXISTS are appropriate. Please manually verify if there are any dynamic query generators that might combine these columns, though current evidence suggests they are sufficient.

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

🧹 Nitpick comments (1)
lib/database.js (1)

115-124: Simplify async functions by removing unnecessary promise chains.

Several functions are marked as async but still use promise chains unnecessarily.

Apply these diffs to improve the implementations:

 async function cancel_all_reminders(pub_key) {
     const q = `
         DELETE FROM reminder_notifications
         WHERE pub_key = $1
      `;
     debug(`Removing reminders for ${pub_key}`);
-    return pool.query(q, [pub_key.toLowerCase()]).then((_) => {
-        return true;
-    });
+    await pool.query(q, [pub_key.toLowerCase()]);
+    return true;
 }

 async function list_reminders(send_at_before) {
     const q = `
         SELECT id, pub_key, reminder_type, send_at 
         FROM reminder_notifications 
         WHERE send_at < $1`;
-    return pool.query(q, [send_at_before]).then((res) => {
-        return res.rows;
-    });
+    const res = await pool.query(q, [send_at_before]);
+    return res.rows;
 }

 async function delete_reminder(id) {
     const q = `
         DELETE FROM reminder_notifications
         WHERE id = $1
      `;
-    return pool.query(q, [id]).then((_) => {
-        return true;
-    });
+    await pool.query(q, [id]);
+    return true;
 }

 async function admin_list() {
     const q = `SELECT platform, updated_at from push_tokens`;
-    return pool.query(q, []).then((res) => {
-        return res.rows;
-    });
+    const res = await pool.query(q, []);
+    return res.rows;
 }

 async function healthCheck() {
     const q = `SELECT 1`;
-    return pool.query(q, []).then((res) => {
-        return res.rowCount > 0;
-    });
+    const res = await pool.query(q, []);
+    return res.rowCount > 0;
 }

Also applies to: 131-139, 146-154, 156-161, 163-168

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9d067 and ba2af66.

📒 Files selected for processing (3)
  • lib/database.js (6 hunks)
  • routes/send.js (6 hunks)
  • routes/sendFirebase.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • routes/send.js
  • routes/sendFirebase.js
🧰 Additional context used
🧠 Learnings (1)
lib/database.js (1)
Learnt from: PanchoBubble
PR: tari-labs/ios_aurora_push#39
File: lib/database.js:24-24
Timestamp: 2025-02-12T13:00:34.820Z
Learning: In the push_tokens table, the WHERE clause `pub_key = $1 OR app_id = $2 OR user_id = $3` is intentionally designed to allow multiple devices to be registered with the same app_id or user_id, returning all matching rows based on any of these identifiers.
🔇 Additional comments (4)
lib/database.js (4)

4-5: Review SSL configuration security implications.

Setting rejectUnauthorized: false disables SSL certificate validation, which could expose the application to man-in-the-middle attacks. In production, proper SSL certificates should be configured.

Consider implementing proper certificate validation for production environments.


16-23: LGTM! Improved null handling and async pattern.

The function has been properly converted to async/await and includes null safety for the pubKey parameter.


32-40: LGTM! Firebase token retrieval implementation.

The function correctly implements the intended behavior of retrieving tokens based on either appId or userId, as confirmed by previous discussions.


53-71: LGTM! Improved error handling and parameter management.

The function has been updated with:

  • Proper async/await pattern
  • Descriptive error message
  • COALESCE for handling nullable app_id and user_id fields

Comment on lines +94 to 108
async function schedule_reminder(pub_key, reminder_type, send_at) {
const q = `
INSERT INTO reminder_notifications (pub_key, reminder_type, send_at)
VALUES($1, $2, $3)
`;
debug(`Scheduling ${reminder_type} notification for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), reminder_type, send_at]).then(res => {
return pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw "reminder_not_scheduled";
throw 'reminder_not_scheduled';
}

return true
return true;
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve async pattern consistency in schedule_reminder.

The function uses promise chains despite being marked as async.

Apply this diff to improve the implementation:

 async function schedule_reminder(pub_key, reminder_type, send_at) {
     const q = `
         INSERT INTO reminder_notifications (pub_key, reminder_type, send_at)
         VALUES($1, $2, $3) 
      `;
     debug(`Scheduling ${reminder_type} notification for ${pub_key}`);
-    return pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]).then((res) => {
-        const success = res.rowCount > 0;
-        if (!success) {
-            throw 'reminder_not_scheduled';
-        }
-
-        return true;
-    });
+    const res = await pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]);
+    if (res.rowCount === 0) {
+        throw new Error('Failed to schedule reminder');
+    }
+    return true;
 }
📝 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
async function schedule_reminder(pub_key, reminder_type, send_at) {
const q = `
INSERT INTO reminder_notifications (pub_key, reminder_type, send_at)
VALUES($1, $2, $3)
`;
debug(`Scheduling ${reminder_type} notification for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), reminder_type, send_at]).then(res => {
return pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw "reminder_not_scheduled";
throw 'reminder_not_scheduled';
}
return true
return true;
});
}
async function schedule_reminder(pub_key, reminder_type, send_at) {
const q = `
INSERT INTO reminder_notifications (pub_key, reminder_type, send_at)
VALUES($1, $2, $3)
`;
debug(`Scheduling ${reminder_type} notification for ${pub_key}`);
const res = await pool.query(q, [pub_key?.toLowerCase(), reminder_type, send_at]);
if (res.rowCount === 0) {
throw new Error('Failed to schedule reminder');
}
return true;
}

Comment on lines +77 to 87
async function delete_token(pub_key, token) {
const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), token]).then(res => {
return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
}
return true
return true;
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve async pattern consistency and error handling.

The function is marked as async but still uses promise chains. Also, the error message includes potentially sensitive token information.

Apply this diff to improve the implementation:

 async function delete_token(pub_key, token) {
     const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
     debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
-    return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
-        const success = res.rowCount > 0;
-        if (!success) {
-            throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
-        }
-        return true;
-    });
+    const res = await pool.query(q, [pub_key.toLowerCase(), token]);
+    if (res.rowCount === 0) {
+        throw new Error('Failed to delete push token');
+    }
+    return true;
 }
📝 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
async function delete_token(pub_key, token) {
const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
return pool.query(q, [pub_key.toLowerCase(), token]).then(res => {
return pool.query(q, [pub_key.toLowerCase(), token]).then((res) => {
const success = res.rowCount > 0;
if (!success) {
throw `pub_key for token ${token.substring(0, 4)}... not deleted.`;
}
return true
return true;
});
}
async function delete_token(pub_key, token) {
const q = `DELETE FROM push_tokens WHERE pub_key = $1 AND token = $2`;
debug(`Removing token ${token.substring(0, 4)}... for ${pub_key}`);
const res = await pool.query(q, [pub_key.toLowerCase(), token]);
if (res.rowCount === 0) {
throw new Error('Failed to delete push token');
}
return true;
}

@PanchoBubble PanchoBubble merged commit a8f05fc into master Feb 12, 2025
1 check passed
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.

1 participant