Skip to content

refactor routes break to files - #26

Merged
gkorland merged 17 commits into
mainfrom
refactor
Aug 2, 2025
Merged

refactor routes break to files#26
gkorland merged 17 commits into
mainfrom
refactor

Conversation

@gkorland

@gkorland gkorland commented Aug 1, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

New Features

  • Added OAuth authentication with Google and GitHub, including session and user identity management.
  • Introduced a Flask application factory for centralized app setup and route registration.
  • Implemented new API blueprints for authentication, graph management, and database connections.
  • Launched a modular chat interface with components for input, sidebar menus, analytics, and instructions.
  • Enabled PostgreSQL database connection via a dedicated modal and API endpoint.
  • Supported graph loading, listing, natural language querying, schema refresh, and streaming multi-step responses.
  • Added confirmation dialogs for destructive SQL operations before execution.
  • Developed a themeable, responsive UI with user profile management and theme toggle.

Bug Fixes

  • Enhanced error handling and logging for OAuth, database connections, and schema refresh.
  • Improved session clearing and redirection on authentication errors.

Style

  • Established a comprehensive CSS framework with variables, themes, animations, responsive layouts, and consistent styling for buttons, modals, menus, and chat components.

Documentation

  • Modularized Jinja2 templates for base layout, chat interface, components, and modals to improve maintainability.

Chores

  • Refactored backend to delegate app setup to a factory function and reorganized routes into blueprints.
  • Structured frontend JavaScript into modular components handling chat, graphs, UI, modals, and messaging logic.

@coderabbitai

coderabbitai Bot commented Aug 1, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a major architectural refactor and modularization of the text2sql API and its web interface. The backend is restructured around a Flask application factory, modular blueprints for authentication, graph, and database routes, and robust OAuth-based authentication with Google and GitHub. The frontend is rebuilt with modular Jinja2 templates and a modern, component-based JavaScript and CSS structure, supporting responsive design, theming, and interactive chat features.

Changes

Cohort / File(s) Change Summary
Backend Application Factory & Entry Point
api/app_factory.py, api/index.py
Introduced a Flask application factory (create_app) to centralize app configuration, OAuth setup, and blueprint registration. The main entry point now delegates all setup to this factory.
Authentication Modules
api/auth/__init__.py, api/auth/oauth_handlers.py, api/auth/user_management.py
Added modular authentication logic: user and identity management, OAuth signal handlers, token validation, and route protection via decorators. Public API is explicitly exported.
API Routing & Blueprints
api/routes/__init__.py, api/routes/auth.py, api/routes/database.py, api/routes/graphs.py
Introduced modular blueprints for authentication, database connection, and graph operations. Each blueprint encapsulates related endpoints, error handling, and streaming logic.
Backend Package Initialization
api/__init__.py
Added a package docstring for the API package.
Postgres Loader
api/loaders/postgres_loader.py
Refined error handling in schema refresh logic, returning generic messages and logging specifics.
Frontend: Base & Page Templates
api/templates/base.j2, api/templates/chat.j2, api/templates/chat_new.j2
Introduced a base Jinja2 template and refactored chat templates to use modular blocks and includes, supporting extensibility and layout consistency.
Frontend: Template Components
api/templates/components/*
Added modular UI components for chat header, input, sidebar, analytics, instructions, modals, toolbar, user profile, and scripts, enabling reusable and maintainable markup.
Frontend: Static CSS
api/static/css/*
Added modular, themeable, and responsive CSS for base styles, layout, chat components, menus, buttons, modals, animations, and variables.
Frontend: Static JavaScript - Main App & Modules
api/static/js/app.js, api/static/js/modules/chat.js, api/static/js/modules/config.js, api/static/js/modules/graphs.js, api/static/js/modules/messages.js, api/static/js/modules/modals.js, api/static/js/modules/ui.js
Introduced a modular JavaScript architecture: main app entry point coordinates chat, UI, modal, and graph modules. Chat logic supports streaming, destructive operation confirmation, and robust error handling. Config and DOM references are centralized.
Frontend: Static JavaScript - Conditional Graph Fetch
api/static/js/chat.js
Updated to fetch graph list only if user is authenticated, guarding fetch logic with an authentication flag.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant FlaskApp
    participant OAuthProvider as Google/GitHub OAuth
    participant DB as Postgres/Graph Loader

    Browser->>FlaskApp: Request /login (Google/GitHub)
    FlaskApp->>OAuthProvider: Redirect for OAuth
    OAuthProvider-->>FlaskApp: OAuth callback with token
    FlaskApp->>FlaskApp: Validate token, fetch user info
    FlaskApp->>DB: Ensure user/identity in Organizations graph
    DB-->>FlaskApp: User info confirmed/created
    FlaskApp->>Browser: Set session, redirect to home

    Browser->>FlaskApp: POST /graphs/<graph_id> (chat query)
    FlaskApp->>DB: Process NL query, generate SQL, check for destructive
    alt Destructive SQL Detected
        FlaskApp-->>Browser: Stream confirmation message
        Browser->>FlaskApp: POST /graphs/<graph_id>/confirm (user confirms)
        FlaskApp->>DB: Execute SQL, refresh schema if needed
        DB-->>FlaskApp: Query results
        FlaskApp-->>Browser: Stream results and explanation
    else Non-destructive SQL
        FlaskApp->>DB: Execute SQL, refresh schema if needed
        DB-->>FlaskApp: Query results
        FlaskApp-->>Browser: Stream results and explanation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

In burrows deep, this rabbit toils,
Refactoring code with modular spoils.
OAuth now guards the warren's gate,
Blueprints and themes make the UI great.
With chat that streams and menus neat,
This hop brings structure—oh, what a feat!
🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1002d36 and df33de8.

📒 Files selected for processing (3)
  • api/static/js/modules/modals.js (1 hunks)
  • api/templates/components/chat_header.j2 (1 hunks)
  • api/templates/components/toolbar.j2 (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @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.

@github-actions

github-actions Bot commented Aug 1, 2025

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@vercel

vercel Bot commented Aug 1, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
queryweaver ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 2, 2025 0:37am

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (5)
api/app_factory.py (1)

60-71: Improve error handler specificity.

The current error handler uses string matching which could incorrectly catch unrelated errors containing "token" or "oauth".

Consider making the error detection more specific:

 @app.errorhandler(Exception)
 def handle_oauth_error(error):
     """Handle OAuth-related errors gracefully"""
-    # Check if it's an OAuth-related error
-    if "token" in str(error).lower() or "oauth" in str(error).lower():
+    # Check if it's an OAuth-related error by examining the exception type and message
+    error_str = str(error).lower()
+    oauth_keywords = ["oauth", "token", "unauthorized", "invalid_token", "token_expired"]
+    is_oauth_error = (
+        any(keyword in error_str for keyword in oauth_keywords) or
+        "flask_dance" in str(type(error)).lower()
+    )
+    
+    if is_oauth_error:
         logging.warning("OAuth error occurred: %s", error)
         from flask import session
         session.clear()
         return redirect(url_for("auth.home"))
api/routes/auth.py (2)

20-23: Remove redundant session clearing logic

The validate_and_cache_user() function already clears the session when no valid authentication is found (line 199 in user_management.py). This additional check is redundant.

 @auth_bp.route("/")
 def home():
     """Home route"""
     user_info, is_authenticated = validate_and_cache_user()
 
-    # If not authenticated through OAuth, check for any stale session data
-    if not is_authenticated and not google.authorized and not github.authorized:
-        session.pop("user_info", None)
-
     return render_template("chat.j2", is_authenticated=is_authenticated, user_info=user_info)

73-81: Remove unnecessary try-except block

Since GitHub token revocation is a no-op, the try-except block serves no purpose.

     # Revoke GitHub OAuth token if authorized
     if github.authorized:
-        try:
-            # GitHub doesn't have a simple revoke endpoint like Google
-            # The token will expire naturally or can be revoked from GitHub settings
-            pass
-        except Exception as e:
-            logging.warning("Error with GitHub token cleanup: %s", e)
+        # GitHub doesn't have a simple revoke endpoint like Google
+        # The token will expire naturally or can be revoked from GitHub settings
+        pass
api/auth/user_management.py (1)

170-183: Consider extracting email fetching logic for GitHub

The GitHub email fetching logic is quite complex. Consider extracting it to a separate helper function for better readability and testability.

def _get_github_primary_email(github_client):
    """Get primary email for GitHub user."""
    email_resp = github_client.get("/user/emails")
    if not email_resp.ok:
        return None
    
    emails = email_resp.json()
    # Find primary email
    for email_obj in emails:
        if email_obj.get("primary", False):
            return email_obj.get("email")
    
    # If no primary email found, use the first one
    return emails[0].get("email") if emails else None

Then use it in the main function:

-                # Get user email (GitHub may require separate call for email)
-                email_resp = github.get("/user/emails")
-                email = None
-                if email_resp.ok:
-                    emails = email_resp.json()
-                    # Find primary email
-                    for email_obj in emails:
-                        if email_obj.get("primary", False):
-                            email = email_obj.get("email")
-                            break
-
-                    # If no primary email found, use the first one
-                    if not email and emails:
-                        email = emails[0].get("email")
+                # Get user email (GitHub may require separate call for email)
+                email = _get_github_primary_email(github)
api/index_original.py (1)

1-937: Consider removing this file after refactoring is complete

This appears to be the original monolithic implementation that has been refactored into separate modules. Once the refactoring is verified to work correctly, this file should be removed to avoid confusion and maintenance burden.

If this file is being kept for reference during the transition, consider:

  1. Adding a clear comment at the top indicating it's deprecated
  2. Moving it to a different location (e.g., docs/legacy/)
  3. Or removing it entirely if the refactoring is complete
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b365bb and 783ce1d.

📒 Files selected for processing (10)
  • api/app_factory.py (1 hunks)
  • api/auth/__init__.py (1 hunks)
  • api/auth/oauth_handlers.py (1 hunks)
  • api/auth/user_management.py (1 hunks)
  • api/index.py (1 hunks)
  • api/index_original.py (1 hunks)
  • api/routes/__init__.py (1 hunks)
  • api/routes/auth.py (1 hunks)
  • api/routes/database.py (1 hunks)
  • api/routes/graphs.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
api/routes/auth.py (1)
api/auth/user_management.py (1)
  • validate_and_cache_user (126-200)
api/auth/oauth_handlers.py (2)
api/auth/user_management.py (1)
  • ensure_user_in_organizations (15-103)
api/index_original.py (3)
  • ensure_user_in_organizations (29-117)
  • google_logged_in (275-313)
  • github_logged_in (316-368)
🪛 GitHub Check: CodeQL
api/routes/graphs.py

[warning] 111-111: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 113-113: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[failure] 130-130: Log Injection
This log entry depends on a user-provided value.


[failure] 144-144: Log Injection
This log entry depends on a user-provided value.


[failure] 175-175: Log Injection
This log entry depends on a user-provided value.


[warning] 459-462: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 464-467: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 471-474: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

api/routes/database.py

[warning] 29-29: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 31-31: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 33-33: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 37-37: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

api/index_original.py

[warning] 482-482: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 484-484: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[failure] 501-501: Log Injection
This log entry depends on a user-provided value.


[failure] 515-515: Log Injection
This log entry depends on a user-provided value.


[failure] 546-546: Log Injection
This log entry depends on a user-provided value.


[warning] 888-891: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 893-896: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 900-903: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 923-923: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 925-925: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 927-927: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 931-931: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[failure] 936-936: Flask app is run in debug mode
A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger.

🪛 ast-grep (0.38.6)
api/index_original.py

[warning] 935-935: Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.
Context: app.run(debug=True)
Note: [CWE-489] Active Debug Code. [REFERENCES]
- https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/

(debug-enabled-python)

🔇 Additional comments (5)
api/routes/__init__.py (1)

1-8: LGTM! Clean and well-structured routes module.

This module follows Python packaging best practices with explicit imports and __all__ declaration for clear API boundaries. The modular structure supports the refactoring objectives effectively.

api/auth/__init__.py (1)

1-18: LGTM! Well-organized authentication module interface.

This module provides a clean centralized interface for authentication functionality, with explicit exports via __all__. The structure follows Python packaging best practices and supports the modular refactoring goals.

api/auth/oauth_handlers.py (1)

15-16: LGTM! Clean function signature for OAuth handler setup.

The function provides a clear interface for registering OAuth signal handlers with the blueprints.

api/app_factory.py (1)

27-58: LGTM! Well-structured OAuth and blueprint setup.

The OAuth configuration properly uses environment variables and the blueprint registration is clean and organized.

api/routes/database.py (1)

11-17: LGTM! Proper authentication and clear endpoint definition.

The endpoint is properly secured with the token_required decorator and has clear documentation.

Comment thread api/app_factory.py Outdated
Comment thread api/auth/oauth_handlers.py
Comment thread api/auth/oauth_handlers.py
Comment thread api/index.py Outdated
Comment thread api/routes/auth.py
Comment on lines +27 to +56
@auth_bp.route("/login")
def login_google():
"""Handle Google OAuth login route."""
if not google.authorized:
return redirect(url_for("google.login"))

try:
resp = google.get("/oauth2/v2/userinfo")
if resp.ok:
google_user = resp.json()
# Normalize user info structure
user_info = {
"id": google_user.get("id"),
"name": google_user.get("name"),
"email": google_user.get("email"),
"picture": google_user.get("picture"),
"provider": "google"
}
session["user_info"] = user_info
session["token_validated_at"] = time.time()
return redirect(url_for("auth.home"))

# OAuth token might be expired, redirect to login
session.clear()
return redirect(url_for("google.login"))
except Exception as e:
logging.error("Google login error: %s", e)
session.clear()
return redirect(url_for("google.login"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Route implementation doesn't match its purpose

This route has several issues:

  1. The function name login_google suggests Google-specific login, but the route path /login is generic
  2. It only handles Google OAuth, ignoring GitHub OAuth
  3. It duplicates user info normalization logic that already exists in validate_and_cache_user()

Consider either:

  • Making this a generic login route that handles both providers
  • Renaming the route to /login/google to match the implementation
  • Removing this route entirely and using the OAuth blueprints' built-in login routes

Here's a suggested fix to make it handle both providers:

-@auth_bp.route("/login")
-def login_google():
-    """Handle Google OAuth login route."""
+@auth_bp.route("/login/<provider>")
+def login(provider):
+    """Handle OAuth login route for specified provider."""
+    if provider == "google":
+        if not google.authorized:
+            return redirect(url_for("google.login"))
+        oauth_client = google
+    elif provider == "github":
+        if not github.authorized:
+            return redirect(url_for("github.login"))
+        oauth_client = github
+    else:
+        return jsonify({"error": "Invalid provider"}), 400
+
+    # The validate_and_cache_user function already handles fetching
+    # and normalizing user info for both providers
+    user_info, is_authenticated = validate_and_cache_user()
+    if is_authenticated:
+        return redirect(url_for("auth.home"))
 
-    if not google.authorized:
-        return redirect(url_for("google.login"))
-
-    try:
-        resp = google.get("/oauth2/v2/userinfo")
-        if resp.ok:
-            google_user = resp.json()
-            # Normalize user info structure
-            user_info = {
-                "id": google_user.get("id"),
-                "name": google_user.get("name"),
-                "email": google_user.get("email"),
-                "picture": google_user.get("picture"),
-                "provider": "google"
-            }
-            session["user_info"] = user_info
-            session["token_validated_at"] = time.time()
-            return redirect(url_for("auth.home"))
-
-        # OAuth token might be expired, redirect to login
-        session.clear()
-        return redirect(url_for("google.login"))
-    except Exception as e:
-        logging.error("Google login error: %s", e)
-        session.clear()
-        return redirect(url_for("google.login"))
+    # OAuth token might be expired, redirect to login
+    session.clear()
+    return redirect(url_for(f"{provider}.login"))
📝 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
@auth_bp.route("/login")
def login_google():
"""Handle Google OAuth login route."""
if not google.authorized:
return redirect(url_for("google.login"))
try:
resp = google.get("/oauth2/v2/userinfo")
if resp.ok:
google_user = resp.json()
# Normalize user info structure
user_info = {
"id": google_user.get("id"),
"name": google_user.get("name"),
"email": google_user.get("email"),
"picture": google_user.get("picture"),
"provider": "google"
}
session["user_info"] = user_info
session["token_validated_at"] = time.time()
return redirect(url_for("auth.home"))
# OAuth token might be expired, redirect to login
session.clear()
return redirect(url_for("google.login"))
except Exception as e:
logging.error("Google login error: %s", e)
session.clear()
return redirect(url_for("google.login"))
@auth_bp.route("/login/<provider>")
def login(provider):
"""Handle OAuth login route for specified provider."""
if provider == "google":
if not google.authorized:
return redirect(url_for("google.login"))
oauth_client = google
elif provider == "github":
if not github.authorized:
return redirect(url_for("github.login"))
oauth_client = github
else:
return jsonify({"error": "Invalid provider"}), 400
# The validate_and_cache_user function already handles fetching
# and normalizing user info for both providers
user_info, is_authenticated = validate_and_cache_user()
if is_authenticated:
return redirect(url_for("auth.home"))
# OAuth token might be expired, redirect to login
session.clear()
return redirect(url_for(f"{provider}.login"))
🤖 Prompt for AI Agents
In api/routes/auth.py around lines 27 to 56, the /login route is misleadingly
named and only handles Google OAuth, ignoring GitHub OAuth and duplicating user
info normalization. To fix this, rename the route to /login/google to reflect
its Google-specific purpose, or refactor the route to handle both Google and
GitHub OAuth logins generically by detecting the provider and delegating
accordingly. Also, remove the duplicated user info normalization logic and
instead call the existing validate_and_cache_user() function to handle user data
processing.

Comment thread api/routes/database.py Outdated
Comment thread api/routes/graphs.py
Comment thread api/routes/graphs.py Outdated
Comment thread api/routes/graphs.py
Comment thread api/routes/database.py Fixed
Comment thread api/routes/database.py Fixed
Comment thread api/routes/database.py Fixed
Comment thread api/routes/database.py Fixed
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
api/routes/database.py (1)

27-42: Address information exposure through exception details.

The code still returns detailed exception messages directly to clients, which risks exposing sensitive system information. This security issue was previously identified and remains unresolved.

Apply this diff to sanitize error messages:

         try:
             # Check for Postgres URL
             if url.startswith("postgres://") or url.startswith("postgresql://"):
                 try:
                     # Attempt to connect/load using the loader
                     success, result = PostgresLoader.load(g.user_id, url)
                     if success:
                         return jsonify({"success": True, "message": result}), 200
 
                     return jsonify({"success": False, "error": result}), 400
-                except (ValueError, ConnectionError) as e:
-                    return jsonify({"success": False, "error": str(e)}), 500
+                except (ValueError, ConnectionError) as e:
+                    import logging
+                    logging.error("Database connection error for user %s: %s", g.user_id, str(e))
+                    return jsonify({"success": False, "error": "Failed to connect to database"}), 500
 
             return jsonify({"success": False, "error": "Invalid Postgres URL"}), 400
-        except (ValueError, TypeError) as e:
-            return jsonify({"success": False, "error": str(e)}), 500
+        except (ValueError, TypeError) as e:
+            import logging
+            logging.error("Unexpected error in database connection: %s", str(e))
+            return jsonify({"success": False, "error": "Internal server error"}), 500
api/routes/graphs.py (3)

109-114: Avoid exposing internal error details.

The error response includes raw error details which could expose sensitive information about system internals.

     # ✅ Return the final response
     if success:
         return jsonify({"message": result, "graph_id": graph_id})
 
-    return jsonify({"error": result}), 400
+    # Log the detailed error internally
+    import logging
+    logging.error("Graph loading failed for user %s: %s", g.user_id, result)
+    # Return generic error to client
+    return jsonify({"error": "Failed to load graph data"}), 400

147-147: Sanitize user input before logging to prevent log injection.

User-provided query data is directly logged without sanitization, which could lead to log injection attacks.

-    logging.info("User Query: %s", queries_history[-1])
+    # Sanitize user input before logging
+    sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+    logging.info("User Query: %s", sanitized_query)

Apply similar sanitization to the other logging statements:

-        logging.info("Calling to relevancy agent with query: %s",
-                     queries_history[-1])
+        sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+        logging.info("Calling to relevancy agent with query: %s",
+                     sanitized_query)
-            logging.info("Calling to analysis agent with query: %s", queries_history[-1])
+            sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+            logging.info("Calling to analysis agent with query: %s", sanitized_query)

Also applies to: 160-161, 192-192


476-491: Avoid exposing internal error details in schema refresh.

The error responses include detailed error messages that could expose sensitive system information.

         if success:
             return jsonify({
                 "success": True,
                 "message": f"Graph schema refreshed successfully. {message}"
             }), 200
         else:
+            logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
             return jsonify({
                 "success": False,
-                "error": f"Failed to refresh schema: {message}"
+                "error": "Failed to refresh schema"
             }), 500
 
     except Exception as e:
         logging.error("Error in manual schema refresh: %s", e)
         return jsonify({
             "success": False,
-            "error": f"Error refreshing schema: {str(e)}"
+            "error": "Error refreshing schema"
         }), 500
🧹 Nitpick comments (4)
api/routes/database.py (1)

22-22: Remove trailing whitespace.

-        return jsonify({"success": False, "error": "No URL provided"}), 400
-        
+        return jsonify({"success": False, "error": "No URL provided"}), 400
+
api/auth/user_management.py (2)

24-38: Remove trailing whitespace and improve email validation.

     # Input validation
     if not provider_user_id or not email or not provider:
-        logging.error("Missing required parameters: provider_user_id=%s, email=%s, provider=%s", 
+        logging.error("Missing required parameters: provider_user_id=%s, email=%s, provider=%s",
                      provider_user_id, email, provider)
         return False, None
-    
+
     # Validate email format (basic check)
     if "@" not in email or "." not in email:
         logging.error("Invalid email format: %s", email)
         return False, None
-        
+
     # Validate provider is in allowed list
     allowed_providers = ["google", "github"]
     if provider not in allowed_providers:
         logging.error("Invalid provider: %s", provider)
         return False, None
-    
+

Consider using a more robust email validation:

import re
email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
if not email_pattern.match(email):
    logging.error("Invalid email format: %s", email)
    return False, None

256-256: Remove trailing whitespace.

         # If no valid authentication found, clear session
         session.clear()
         return None, False
-        
+
api/routes/graphs.py (1)

125-145: Remove trailing whitespace.

     # Input validation
     if not graph_id or not isinstance(graph_id, str):
         return jsonify({"error": "Invalid graph_id"}), 400
-        
+
     # Sanitize graph_id to prevent injection
     graph_id = graph_id.strip()[:100]  # Limit length and strip whitespace
     if not graph_id:
         return jsonify({"error": "Invalid graph_id"}), 400
-        
+
     graph_id = g.user_id + "_" + graph_id
     request_data = request.get_json()
-    
+
     if not request_data:
         return jsonify({"error": "No JSON data provided"}), 400
-        
+
     queries_history = request_data.get("chat")
     result_history = request_data.get("result")
     instructions = request_data.get("instructions")
-    
+
     if not queries_history or not isinstance(queries_history, list):
         return jsonify({"error": "Invalid or missing chat history"}), 400
-        
+
     if len(queries_history) == 0:
         return jsonify({"error": "Empty chat history"}), 400
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 783ce1d and e243ade.

📒 Files selected for processing (5)
  • api/auth/oauth_handlers.py (1 hunks)
  • api/auth/user_management.py (1 hunks)
  • api/routes/auth.py (1 hunks)
  • api/routes/database.py (1 hunks)
  • api/routes/graphs.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/auth/oauth_handlers.py
  • api/routes/auth.py
🧰 Additional context used
🪛 GitHub Actions: Pylint
api/auth/user_management.py

[warning] 24-256: Pylint: Multiple trailing whitespace issues and code style warnings including too many local variables (17/15), too many return statements (9/6), too many nested blocks (7/5), too many branches (15/12), broad exception catching (broad-exception-caught), and unnecessary elif after return (no-else-return)

api/routes/graphs.py

[warning] 41-486: Pylint: Multiple issues including trailing whitespace, too many return statements (8/6), too many local variables (26/15), broad exception catching, too many branches (17/12), too many statements (up to 87/50), and unnecessary else after return

api/routes/database.py

[warning] 13-26: Pylint: Too many return statements (7/6) and trailing whitespace (trailing-whitespace)

🪛 GitHub Check: CodeQL
api/routes/graphs.py

[warning] 111-111: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 113-113: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[failure] 147-147: Log Injection
This log entry depends on a user-provided value.


[failure] 161-161: Log Injection
This log entry depends on a user-provided value.


[failure] 192-192: Log Injection
This log entry depends on a user-provided value.


[warning] 476-479: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 481-484: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 488-491: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

api/routes/database.py

[warning] 34-34: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 36-36: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 38-38: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 42-42: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

🔇 Additional comments (3)
api/auth/user_management.py (1)

263-286: Well-implemented authentication decorator.

The decorator properly handles authentication, sets the user context, and includes comprehensive error handling with session cleanup for security.

api/routes/graphs.py (2)

25-36: Good implementation of user-scoped graph listing.

The function properly filters graphs by user ID prefix, ensuring users only see their own graphs.


346-450: Well-implemented destructive operation confirmation.

The function properly handles user confirmation for destructive SQL operations, with appropriate execution flow and error handling.

Comment on lines +126 to +139
def update_identity_last_login(provider, provider_user_id):
"""Update the last login timestamp for an existing identity"""
# Input validation
if not provider or not provider_user_id:
logging.error("Missing required parameters: provider=%s, provider_user_id=%s",
provider, provider_user_id)
return

# Validate provider is in allowed list
allowed_providers = ["google", "github"]
if provider not in allowed_providers:
logging.error("Invalid provider: %s", provider)
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove trailing whitespace and extract provider validation.

+# Define at module level
+ALLOWED_PROVIDERS = ["google", "github"]
+
 def update_identity_last_login(provider, provider_user_id):
     """Update the last login timestamp for an existing identity"""
     # Input validation
     if not provider or not provider_user_id:
-        logging.error("Missing required parameters: provider=%s, provider_user_id=%s", 
+        logging.error("Missing required parameters: provider=%s, provider_user_id=%s",
                      provider, provider_user_id)
         return
-        
+
     # Validate provider is in allowed list
-    allowed_providers = ["google", "github"]
-    if provider not in allowed_providers:
+    if provider not in ALLOWED_PROVIDERS:
         logging.error("Invalid provider: %s", provider)
         return
-    
+

Also update the same validation in ensure_user_in_organizations function to use ALLOWED_PROVIDERS.

📝 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
def update_identity_last_login(provider, provider_user_id):
"""Update the last login timestamp for an existing identity"""
# Input validation
if not provider or not provider_user_id:
logging.error("Missing required parameters: provider=%s, provider_user_id=%s",
provider, provider_user_id)
return
# Validate provider is in allowed list
allowed_providers = ["google", "github"]
if provider not in allowed_providers:
logging.error("Invalid provider: %s", provider)
return
# Define at module level
ALLOWED_PROVIDERS = ["google", "github"]
def update_identity_last_login(provider, provider_user_id):
"""Update the last login timestamp for an existing identity"""
# Input validation
if not provider or not provider_user_id:
logging.error("Missing required parameters: provider=%s, provider_user_id=%s",
provider, provider_user_id)
return
# Validate provider is in allowed list
if provider not in ALLOWED_PROVIDERS:
logging.error("Invalid provider: %s", provider)
return
🤖 Prompt for AI Agents
In api/auth/user_management.py around lines 126 to 139, remove any trailing
whitespace in the update_identity_last_login function and extract the provider
validation logic into a reusable constant named ALLOWED_PROVIDERS. Then update
the provider validation in both update_identity_last_login and
ensure_user_in_organizations functions to use this ALLOWED_PROVIDERS constant
for consistency.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
api/templates/components/sidebar_menu.j2 (1)

4-8: Add accessible label for the close button.

The close button lacks proper accessibility attributes. Consider adding aria-label for screen readers since the SVG icon may not be descriptive enough.

-        <button class="action-button" id="menu-button" title="Close Menu">
+        <button class="action-button" id="menu-button" title="Close Menu" aria-label="Close Menu">
api/templates/components/menu_analytics.j2 (1)

2-17: Consider consistent default values and heading semantics.

The analytics sections have inconsistent default values (confidence shows "0%" while others are empty) and could benefit from proper heading hierarchy.

Consider these improvements:

 <div class="menu-item" id="conf-container">
-    <h2>CONFIDENCE VALUE</h2>
+    <h3>CONFIDENCE VALUE</h3>
     <p class="menu-value" id="conf-value">0%</p>
 </div>
 <div class="menu-item" id="exp-container">
-    <h2>EXPLANATION</h2>
-    <div class="menu-value" id="exp-value"></div>
+    <h3>EXPLANATION</h3>
+    <div class="menu-value" id="exp-value">No explanation available</div>
 </div>
 <div class="menu-item" id="info-container">
-    <h2>MISSING INFORMATION</h2>
-    <div class="menu-value" id="info-value"></div>
+    <h3>MISSING INFORMATION</h3>
+    <div class="menu-value" id="info-value">None</div>
 </div>
 <div class="menu-item" id="amb-container">
-    <h2>AMBIGUITY</h2>
-    <div class="menu-value" id="amb-value"></div>
+    <h3>AMBIGUITY</h3>
+    <div class="menu-value" id="amb-value">None detected</div>
 </div>
api/templates/components/user_profile.j2 (1)

11-11: Consider using a form for logout action.

While the current logout implementation works, using a POST form with CSRF protection would be more secure than a GET request.

-        <button onclick="window.location.href='/logout'" class="user-profile-logout">Logout</button>
+        <form method="POST" action="/logout" style="display: inline;">
+            <button type="submit" class="user-profile-logout">Logout</button>
+        </form>
api/templates/components/toolbar.j2 (1)

4-25: Consider SVG optimization for performance.

The inline SVG with multiple icons and clip paths is functional but could be optimized for better performance, especially if used multiple times.

Consider extracting the SVG definitions to a separate symbol definition or using CSS to control visibility instead of multiple embedded icons:

+<svg style="display: none;">
+    <defs>
+        <symbol id="sun-icon" viewBox="0 0 24 24">
+            <circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2"/>
+            <path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" stroke="currentColor" stroke-width="2"/>
+        </symbol>
+        <!-- Define other symbols similarly -->
+    </defs>
+</svg>
api/templates/chat.j2 (1)

28-28: Consider moving the gradient element to the base template

The #gradient div appears to be a visual styling element. If this gradient effect is intended to be used across multiple pages, consider moving it to the base template (base.j2) for better reusability and consistency.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e243ade and 18c79cd.

📒 Files selected for processing (15)
  • api/__init__.py (1 hunks)
  • api/templates/base.j2 (1 hunks)
  • api/templates/chat.j2 (1 hunks)
  • api/templates/chat_new.j2 (1 hunks)
  • api/templates/components/chat_header.j2 (1 hunks)
  • api/templates/components/chat_input.j2 (1 hunks)
  • api/templates/components/login_modal.j2 (1 hunks)
  • api/templates/components/menu_analytics.j2 (1 hunks)
  • api/templates/components/menu_instructions.j2 (1 hunks)
  • api/templates/components/postgres_modal.j2 (1 hunks)
  • api/templates/components/reset_modal.j2 (1 hunks)
  • api/templates/components/scripts.j2 (1 hunks)
  • api/templates/components/sidebar_menu.j2 (1 hunks)
  • api/templates/components/toolbar.j2 (1 hunks)
  • api/templates/components/user_profile.j2 (1 hunks)
✅ Files skipped from review due to trivial changes (9)
  • api/templates/components/menu_instructions.j2
  • api/templates/components/reset_modal.j2
  • api/templates/components/login_modal.j2
  • api/init.py
  • api/templates/components/chat_input.j2
  • api/templates/components/scripts.j2
  • api/templates/components/postgres_modal.j2
  • api/templates/base.j2
  • api/templates/chat_new.j2
🔇 Additional comments (7)
api/templates/components/sidebar_menu.j2 (1)

11-15: Excellent modular structure.

The component-based approach with clear separation and semantic organization makes the code maintainable and reusable.

api/templates/components/menu_analytics.j2 (1)

1-17: Well-structured analytics display component.

The consistent structure and clear ID naming make this component easy to integrate with JavaScript for dynamic updates.

api/templates/components/chat_header.j2 (2)

10-13: Excellent accessibility implementation for file upload.

The hidden file input with custom label and proper attributes (including tabindex="-1") follows accessibility best practices while maintaining custom styling.


3-3: Good use of Flask's url_for for static assets.

Using url_for('static', filename='...') is a security best practice that prevents hardcoded paths and ensures proper URL generation.

api/templates/components/toolbar.j2 (1)

39-41: Well-implemented conditional user interface.

The conditional rendering based on authentication status and user information availability is clean and follows good separation of concerns.

api/templates/chat.j2 (2)

1-40: Well-structured template refactoring!

The refactoring from a standalone HTML document to a modular Jinja2 template using inheritance and component includes is well-executed. The use of blocks (title, toolbar, content, modals, scripts) provides excellent flexibility for customization.


37-40: Correct implementation of script inheritance

Good use of {{ super() }} to preserve parent scripts before including chat-specific scripts. This ensures proper script loading order.

Comment thread api/templates/components/chat_header.j2 Outdated
Comment on lines +18 to +19
<p id="rights-reserved">All Rights Reserved (c) FalkorDB 2025 | <a class="link"
href="https://falkordb.com" target="_blank">falkordb.com</a></p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add security attributes to external link.

External links with target="_blank" should include rel="noopener noreferrer" to prevent potential security vulnerabilities and improve performance.

-        <p id="rights-reserved">All Rights Reserved (c) FalkorDB 2025 | <a class="link"
-                href="https://falkordb.com" target="_blank">falkordb.com</a></p>
+        <p id="rights-reserved">All Rights Reserved (c) FalkorDB 2025 | <a class="link"
+                href="https://falkordb.com" target="_blank" rel="noopener noreferrer">falkordb.com</a></p>
📝 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
<p id="rights-reserved">All Rights Reserved (c) FalkorDB 2025 | <a class="link"
href="https://falkordb.com" target="_blank">falkordb.com</a></p>
<p id="rights-reserved">All Rights Reserved (c) FalkorDB 2025 | <a class="link"
href="https://falkordb.com" target="_blank" rel="noopener noreferrer">falkordb.com</a></p>
🤖 Prompt for AI Agents
In api/templates/components/sidebar_menu.j2 around lines 18 to 19, the external
link with target="_blank" is missing the security attributes rel="noopener
noreferrer". Add rel="noopener noreferrer" to the anchor tag to prevent security
vulnerabilities and improve performance when opening the link in a new tab.

Comment thread api/templates/components/toolbar.j2 Outdated
Comment thread api/templates/components/toolbar.j2 Outdated
Comment on lines +29 to +37
<a href="https://github.com/FalkorDB/QueryWeaver" target="_blank" class="github-link" id="github-link-btn" title="View QueryWeaver on GitHub">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/>
</svg>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="margin-left: 4px;">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
<span id="github-stars" style="margin-left: 2px; font-size: 12px;">-</span>
</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add security attributes to GitHub link.

External links with target="_blank" should include rel="noopener noreferrer" to prevent potential security vulnerabilities.

-<a href="https://github.com/FalkorDB/QueryWeaver" target="_blank" class="github-link" id="github-link-btn" title="View QueryWeaver on GitHub">
+<a href="https://github.com/FalkorDB/QueryWeaver" target="_blank" rel="noopener noreferrer" class="github-link" id="github-link-btn" title="View QueryWeaver on GitHub">
📝 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
<a href="https://github.com/FalkorDB/QueryWeaver" target="_blank" class="github-link" id="github-link-btn" title="View QueryWeaver on GitHub">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/>
</svg>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="margin-left: 4px;">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
<span id="github-stars" style="margin-left: 2px; font-size: 12px;">-</span>
</a>
<a href="https://github.com/FalkorDB/QueryWeaver" target="_blank" rel="noopener noreferrer" class="github-link" id="github-link-btn" title="View QueryWeaver on GitHub">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/>
</svg>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="margin-left: 4px;">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
<span id="github-stars" style="margin-left: 2px; font-size: 12px;">-</span>
</a>
🤖 Prompt for AI Agents
In api/templates/components/toolbar.j2 around lines 29 to 37, the anchor tag
with target="_blank" for the GitHub link is missing the security attributes
rel="noopener noreferrer". Add rel="noopener noreferrer" to the anchor tag to
prevent potential security vulnerabilities when opening external links in a new
tab.

Comment on lines +2 to +4
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
<img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential XSS vulnerability with user data.

User-provided data (user_info.picture, user_info.name) is being output without explicit escaping. While Jinja2 auto-escapes by default, it's safer to be explicit, especially for image sources.

Consider validating the image source on the backend or using a default avatar for security:

-    <img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
+    <img src="{{ user_info.picture | e }}" alt="{{ (user_info.name[0] | upper) if user_info.name else 'U' }}" class="user-profile-img">
📝 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
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
<img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
</button>
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
<img src="{{ user_info.picture | e }}" alt="{{ (user_info.name[0] | upper) if user_info.name else 'U' }}" class="user-profile-img">
</button>
🤖 Prompt for AI Agents
In api/templates/components/user_profile.j2 around lines 2 to 4, user-provided
data like user_info.picture and user_info.name are output without explicit
escaping, posing a potential XSS risk. To fix this, explicitly escape these
variables using Jinja2's escape filter or ensure safe rendering. Additionally,
implement backend validation for the image source or provide a default avatar if
the source is invalid or unsafe.

Comment on lines +2 to +13
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
<img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
</button>
<div id="user-profile-dropdown" class="user-profile-dropdown">
<div class="user-profile-info">
<div class="user-profile-name">{{ user_info.name }}</div>
<div class="user-profile-email">{{ user_info.email }}</div>
</div>
<div class="user-profile-actions">
<button onclick="window.location.href='/logout'" class="user-profile-logout">Logout</button>
</div>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve dropdown accessibility.

The dropdown lacks proper ARIA attributes for accessibility compliance and keyboard navigation support.

-<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
+<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}" 
+        aria-haspopup="true" aria-expanded="false" aria-controls="user-profile-dropdown">
     <img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
 </button>
-<div id="user-profile-dropdown" class="user-profile-dropdown">
+<div id="user-profile-dropdown" class="user-profile-dropdown" role="menu" aria-labelledby="user-profile-btn">
📝 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
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}">
<img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
</button>
<div id="user-profile-dropdown" class="user-profile-dropdown">
<div class="user-profile-info">
<div class="user-profile-name">{{ user_info.name }}</div>
<div class="user-profile-email">{{ user_info.email }}</div>
</div>
<div class="user-profile-actions">
<button onclick="window.location.href='/logout'" class="user-profile-logout">Logout</button>
</div>
</div>
<button id="user-profile-btn" class="user-profile-btn" title="{{ user_info.name }}"
aria-haspopup="true" aria-expanded="false" aria-controls="user-profile-dropdown">
<img src="{{ user_info.picture }}" alt="{{ user_info.name[0] | upper }}" class="user-profile-img">
</button>
<div id="user-profile-dropdown" class="user-profile-dropdown" role="menu" aria-labelledby="user-profile-btn">
<div class="user-profile-info">
<div class="user-profile-name">{{ user_info.name }}</div>
<div class="user-profile-email">{{ user_info.email }}</div>
</div>
<div class="user-profile-actions">
<button onclick="window.location.href='/logout'" class="user-profile-logout">Logout</button>
</div>
</div>
🤖 Prompt for AI Agents
In api/templates/components/user_profile.j2 between lines 2 and 13, the user
profile dropdown lacks ARIA attributes and keyboard navigation support, which
reduces accessibility. Add appropriate ARIA roles such as role="button" and
aria-haspopup="true" to the button, and aria-expanded to reflect the dropdown
state. Also, ensure the dropdown container has role="menu" and each action has
role="menuitem". Implement keyboard event handlers to allow toggling the
dropdown with keyboard keys like Enter and Escape for better accessibility.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
api/static/css/chat.css (1)

1251-1253: Consider restoring a distinct colour for emphasis.

strong text in destructive confirmations now uses the same colour as the surrounding text, which removes the visual emphasis that the previous light-red (#FFCDD2) provided. A softer but still distinct accent would preserve accessibility while remaining on-brand:

-.confirmation-text strong {
-  color: var(--text-primary);
-  font-weight: bold;
+.confirmation-text strong {
+  color: var(--falkor-accent); /* or another semantically appropriate var */
+  font-weight: bold;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 18c79cd and 77c00ff.

📒 Files selected for processing (1)
  • api/static/css/chat.css (1 hunks)
🔇 Additional comments (1)
api/static/css/chat.css (1)

1246-1246: Theme-variable switch looks good.

Replacing the hard-coded #ffffff with var(--text-primary) keeps the text readable in both light and dark themes and aligns with the rest of the palette.

# Log the error and return failure
logging.error("Error refreshing graph schema: %s", str(e))
error_msg = "Error refreshing graph schema"
logging.error(error_msg)

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.
This log entry depends on a
user-provided value
.

Copilot Autofix

AI about 1 year ago

To fix this log injection vulnerability, we need to sanitize the graph_id before logging it. Specifically, we should remove or replace any newline (\n) and carriage return (\r) characters from graph_id before it is passed to the logger. The best way to do this is to define a small helper function (e.g., _sanitize_for_log) that strips or replaces these characters, and use it on graph_id in the logging call on line 406. This change should be made directly in api/loaders/postgres_loader.py within the refresh_graph_schema method. The helper function can be defined as a static method within the PostgresLoader class, or as a local function within the method if you prefer minimal scope.


Suggested changeset 1
api/loaders/postgres_loader.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/loaders/postgres_loader.py b/api/loaders/postgres_loader.py
--- a/api/loaders/postgres_loader.py
+++ b/api/loaders/postgres_loader.py
@@ -22,2 +22,11 @@
 
+    @staticmethod
+    def _sanitize_for_log(value: str) -> str:
+        """
+        Sanitize a string for safe logging by removing CR and LF characters.
+        """
+        if not isinstance(value, str):
+            return value
+        return value.replace('\r', '').replace('\n', '')
+
     # DDL operations that modify database schema
@@ -405,3 +414,3 @@
 
-            logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+            logging.error("Schema refresh failed for graph %s: %s", PostgresLoader._sanitize_for_log(graph_id), message)
             return False, "Failed to reload schema"
EOF
@@ -22,2 +22,11 @@

@staticmethod
def _sanitize_for_log(value: str) -> str:
"""
Sanitize a string for safe logging by removing CR and LF characters.
"""
if not isinstance(value, str):
return value
return value.replace('\r', '').replace('\n', '')

# DDL operations that modify database schema
@@ -405,3 +414,3 @@

logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
logging.error("Schema refresh failed for graph %s: %s", PostgresLoader._sanitize_for_log(graph_id), message)
return False, "Failed to reload schema"
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread api/routes/graphs.py Fixed
Comment thread api/routes/graphs.py
"message": f"Graph schema refreshed successfully. {message}"
}), 200

logging.error("Schema refresh failed for graph %s: %s", graph_id, message)

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI about 1 year ago

To fix the log injection vulnerability, we should sanitize the user-provided graph_id before logging it. Specifically, we should remove any newline characters (\n, \r) from graph_id before it is used in the log entry. The best way to do this is to create a small helper function (e.g., sanitize_log_input) that strips these characters, and use it to sanitize graph_id before logging. This change should be made directly in the region where graph_id is logged (line 489). The helper function can be defined near the top of the file, and used wherever user input is logged.

Required changes:

  • Add a helper function sanitize_log_input to remove \n and \r from strings.
  • Use this function to sanitize graph_id in the log entry on line 489.

Suggested changeset 1
api/routes/graphs.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/routes/graphs.py b/api/routes/graphs.py
--- a/api/routes/graphs.py
+++ b/api/routes/graphs.py
@@ -4,2 +4,3 @@
 import logging
+
 from concurrent.futures import ThreadPoolExecutor
@@ -488,3 +489,3 @@
 
-        logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+        logging.error("Schema refresh failed for graph %s: %s", sanitize_log_input(graph_id), message)
         return jsonify({
EOF
@@ -4,2 +4,3 @@
import logging

from concurrent.futures import ThreadPoolExecutor
@@ -488,3 +489,3 @@

logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
logging.error("Schema refresh failed for graph %s: %s", sanitize_log_input(graph_id), message)
return jsonify({
Copilot is powered by AI and may make mistakes. Always verify output.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (7)
api/loaders/postgres_loader.py (1)

406-407: Fix log injection vulnerability

User-provided graph_id is logged directly without sanitization, which could lead to log injection attacks.

Apply this fix to sanitize the graph_id before logging:

-            logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+            # Sanitize graph_id to prevent log injection
+            sanitized_graph_id = graph_id.replace('\n', ' ').replace('\r', ' ')[:100]
+            logging.error("Schema refresh failed for graph %s: %s", sanitized_graph_id, message)
api/routes/graphs.py (6)

147-147: Sanitize user input before logging to prevent log injection

User-provided query data is directly logged without sanitization, which could lead to log injection attacks.

Apply this sanitization:

-    logging.info("User Query: %s", queries_history[-1])
+    # Sanitize user input before logging
+    sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+    logging.info("User Query: %s", sanitized_query)

160-162: Sanitize user input before logging

Same log injection vulnerability as above.

Apply this sanitization:

-        logging.info("Calling to relevancy agent with query: %s",
-                     queries_history[-1])
+        sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+        logging.info("Calling to relevancy agent with query: %s", sanitized_query)

192-192: Sanitize user input before logging

Same log injection vulnerability as above.

Apply this sanitization:

-            logging.info("Calling to analysis agent with query: %s", queries_history[-1])
+            sanitized_query = queries_history[-1].replace('\n', ' ').replace('\r', ' ')[:500]
+            logging.info("Calling to analysis agent with query: %s", sanitized_query)

109-114: Avoid exposing internal error details

The error response includes raw error details which could expose sensitive information about system internals.

Apply this fix:

     # ✅ Return the final response
     if success:
         return jsonify({"message": result, "graph_id": graph_id})
 
-    return jsonify({"error": result}), 400
+    # Log the detailed error internally
+    logging.error("Graph loading failed: %s", result)
+    # Return generic error to client
+    return jsonify({"error": "Failed to load graph data"}), 400

476-485: Avoid exposing internal error details in schema refresh

The error responses include detailed error messages that could expose sensitive system information.

Apply this fix:

         if success:
             return jsonify({
                 "success": True,
                 "message": f"Graph schema refreshed successfully. {message}"
             }), 200
-        else:
-            return jsonify({
-                "success": False,
-                "error": f"Failed to refresh schema: {message}"
-            }), 500
+        
+        logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+        return jsonify({
+            "success": False,
+            "error": "Failed to refresh schema"
+        }), 500

487-492: Avoid exposing internal error details in exception handling

Similar issue with exposing internal error details.

Apply this fix:

     except Exception as e:
         logging.error("Error in manual schema refresh: %s", e)
         return jsonify({
             "success": False,
-            "error": f"Error refreshing schema: {str(e)}"
+            "error": "Error refreshing schema"
         }), 500
🧹 Nitpick comments (2)
api/loaders/postgres_loader.py (1)

411-414: Remove duplicate logging

The error message is logged twice - once on line 411 and again on line 413, which is redundant.

Apply this diff to remove the duplicate logging:

             # Log the error and return failure
             logging.error("Error refreshing graph schema: %s", str(e))
             error_msg = "Error refreshing graph schema"
-            logging.error(error_msg)
             return False, error_msg
api/routes/graphs.py (1)

116-343: Consider breaking down the complex query_graph function

This function has high complexity with 17 branches, 26 local variables, and 87 statements, exceeding Pylint limits. Consider extracting helper functions for better maintainability.

Consider extracting these logical segments into separate functions:

  • Input validation and sanitization
  • Relevancy analysis logic
  • SQL execution and result handling
  • Schema refresh logic
  • Response formatting

This would improve readability and make the code easier to test and maintain.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c46be44 and 09ba7d8.

📒 Files selected for processing (2)
  • api/loaders/postgres_loader.py (1 hunks)
  • api/routes/graphs.py (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
api/loaders/postgres_loader.py

[failure] 406-406: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.

api/routes/graphs.py

[warning] 111-111: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 113-113: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[failure] 147-147: Log Injection
This log entry depends on a user-provided value.


[failure] 161-161: Log Injection
This log entry depends on a user-provided value.


[failure] 192-192: Log Injection
This log entry depends on a user-provided value.


[warning] 476-479: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[failure] 481-481: Log Injection
This log entry depends on a user-provided value.

🪛 GitHub Actions: Pylint
api/loaders/postgres_loader.py

[warning] 59-491: Pylint: Unnecessary 'elif' after 'return', broad exception catching and raising, import outside toplevel

api/routes/graphs.py

[warning] 41-487: Pylint: Multiple issues including trailing whitespace, too many return statements (8/6), too many local variables (26/15), too many branches (17/12), too many statements (up to 87/50), and broad exception catching

🔇 Additional comments (1)
api/loaders/postgres_loader.py (1)

405-408: Improved error handling approach

Good security practice to log detailed errors internally while returning generic error messages to prevent information leakage.

Comment thread api/routes/graphs.py
"message": f"Graph schema refreshed successfully. {message}"
}), 200

logging.error("Schema refresh failed for graph %s: %s", graph_id, message)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix log injection in error logging

User-provided graph_id is logged directly without sanitization.

Apply this sanitization:

-        logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+        # Sanitize graph_id to prevent log injection
+        sanitized_graph_id = graph_id.replace('\n', ' ').replace('\r', ' ')[:100]
+        logging.error("Schema refresh failed for graph %s: %s", sanitized_graph_id, message)
📝 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
logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
# Sanitize graph_id to prevent log injection
sanitized_graph_id = graph_id.replace('\n', ' ').replace('\r', ' ')[:100]
logging.error("Schema refresh failed for graph %s: %s", sanitized_graph_id, message)
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 481-481: Log Injection
This log entry depends on a user-provided value.

🤖 Prompt for AI Agents
In api/routes/graphs.py at line 481, the error log directly includes the
user-provided graph_id, which risks log injection. To fix this, sanitize or
escape the graph_id before logging it by applying a safe encoding or filtering
method to remove or neutralize any malicious characters. Then log the sanitized
version instead of the raw graph_id.

Comment thread api/routes/database.py Fixed
Comment thread api/routes/database.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
api/routes/database.py (1)

27-41: Information exposure issue persists from previous review.

The static analysis correctly identifies that detailed exception information is still being exposed to clients at lines 34 and 37. This is the same issue flagged in previous reviews.

The previous review recommendation to sanitize error messages wasn't fully implemented. The result from PostgresLoader.load could still contain sensitive system details.

Apply this additional diff to ensure complete sanitization:

-                return jsonify({"success": False, "error": result}), 400
+                return jsonify({"success": False, "error": "Database connection failed"}), 400
🧹 Nitpick comments (1)
api/routes/database.py (1)

42-45: Consider refactoring to reduce complexity and return statements.

The function has 7 return statements, triggering the pylint warning. The nested try-catch structure also adds complexity.

Consider refactoring to a more linear structure:

def connect_database():
    """
    Accepts a JSON payload with a Postgres URL and attempts to connect.
    Returns success or error message.
    """
    # Validate input
    data = request.get_json()
    url = data.get("url") if data else None
    
    if not url:
        return jsonify({"success": False, "error": "No URL provided"}), 400
    
    if not isinstance(url, str) or len(url.strip()) == 0:
        return jsonify({"success": False, "error": "Invalid URL format"}), 400
    
    if not (url.startswith("postgres://") or url.startswith("postgresql://")):
        return jsonify({"success": False, "error": "Invalid Postgres URL"}), 400
    
    # Attempt database connection
    try:
        success, result = PostgresLoader.load(g.user_id, url)
        if success:
            return jsonify({"success": True, "message": result}), 200
        else:
            return jsonify({"success": False, "error": "Database connection failed"}), 400
    except (ValueError, ConnectionError) as e:
        logging.error("Database connection error: %s", str(e))
        return jsonify({"success": False, "error": "Failed to connect to database"}), 500
    except Exception as e:
        logging.error("Unexpected error in database connection: %s", str(e))
        return jsonify({"success": False, "error": "Internal server error"}), 500
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 09ba7d8 and b21f878.

📒 Files selected for processing (1)
  • api/routes/database.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/routes/database.py (2)
api/auth/user_management.py (1)
  • token_required (263-285)
api/loaders/postgres_loader.py (1)
  • PostgresLoader (18-491)
🪛 GitHub Check: CodeQL
api/routes/database.py

[warning] 34-34: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.


[warning] 37-37: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.
Stack trace information flows to this location and may be exposed to an external user.

🪛 GitHub Actions: Pylint
api/routes/database.py

[warning] 13-13: Pylint: Too many return statements (7/6) (too-many-return-statements)

🔇 Additional comments (5)
api/routes/database.py (5)

1-9: LGTM!

The imports and blueprint setup follow Flask best practices and are appropriate for the functionality.


11-12: LGTM!

The route definition and authentication decorator are correctly implemented and follow established patterns.


13-17: LGTM!

The function definition and docstring clearly describe the intended functionality.


18-21: LGTM!

The request data extraction safely handles missing JSON data and properly validates the presence of the URL.


24-25: LGTM!

The URL format validation properly checks for string type and non-empty content.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
api/routes/graphs.py (1)

489-489: Fix log injection vulnerability.

The graph_id parameter is logged directly without sanitization, creating a log injection vulnerability as identified by static analysis.

-        logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+        # Sanitize graph_id to prevent log injection
+        sanitized_graph_id = graph_id.replace('\n', ' ').replace('\r', ' ')[:100]
+        logging.error("Schema refresh failed for graph %s: %s", sanitized_graph_id, message)
🧹 Nitpick comments (2)
api/routes/graphs.py (2)

121-351: Consider refactoring this complex function.

This function has high complexity with multiple responsibilities (validation, streaming, SQL generation, execution, schema refresh). Consider breaking it into smaller, focused functions to improve maintainability and testability.

The function could be split into:

  • Input validation helper
  • SQL generation and analysis logic
  • Destructive operation detection
  • Query execution and result streaming
  • Schema refresh handling

1-500: Address code quality improvements for better maintainability.

The pipeline indicates several code quality issues including high complexity, too many branches/returns, and trailing whitespace. While the functionality is correct, consider:

  • Breaking down complex functions (especially query_graph)
  • Using more specific exception handling where possible
  • Running a code formatter to address whitespace issues

These improvements will enhance code maintainability and readability.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b21f878 and cfc80be.

📒 Files selected for processing (4)
  • api/routes/database.py (1 hunks)
  • api/routes/graphs.py (1 hunks)
  • api/static/js/chat.js (1 hunks)
  • api/templates/components/sidebar_menu.j2 (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • api/templates/components/sidebar_menu.j2
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/routes/database.py
🧰 Additional context used
🪛 GitHub Check: CodeQL
api/routes/graphs.py

[failure] 489-489: Log Injection
This log entry depends on a user-provided value.

🪛 GitHub Actions: Pylint
api/routes/graphs.py

[warning] 44-495: Too many return statements (R0911), too many local variables (R0914), too many branches (R0912), too many statements (R0915), broad exception caught (W0718), trailing whitespace (C0303).

🔇 Additional comments (7)
api/static/js/chat.js (1)

611-616: LGTM! Clean authentication check implementation.

The conditional fetching based on authentication status is well-implemented. The fallback to false when window.isAuthenticated is undefined is a good defensive programming practice, and preventing the API call when unauthenticated aligns perfectly with the backend's token-required authentication system.

api/routes/graphs.py (6)

1-27: LGTM! Well-organized imports and security utilities.

The imports are appropriate for the functionality, and the sanitize_query function is a good security practice to prevent injection attacks. The MESSAGE_DELIMITER constant ensures consistency with the frontend implementation.


28-39: LGTM! Secure user-scoped graph listing.

The route properly implements user isolation by filtering graphs with the user_id prefix and uses the token_required decorator for authentication. The implementation ensures users can only see their own graphs.


42-118: LGTM! Secure file upload handling with proper error management.

The route correctly handles multiple content types and file uploads with appropriate validation. The error handling follows security best practices by logging detailed errors internally while returning generic messages to users, addressing previous security concerns about information exposure.


152-152: Excellent security improvement with query sanitization.

The use of sanitize_query() for logging user input effectively prevents log injection attacks. This addresses the security concerns raised in previous reviews.

Also applies to: 165-166, 198-199


354-457: LGTM! Well-structured confirmation flow.

The destructive operation confirmation flow is well-implemented with proper streaming responses and clear user feedback. The separation of concerns between detection and confirmation improves security by requiring explicit user consent.


460-500: LGTM! Clean manual schema refresh implementation.

The route provides a useful manual refresh capability with proper authentication and error handling. The generic error responses protect against information disclosure while logging detailed errors internally.

Comment thread api/routes/graphs.py Outdated
Comment thread api/routes/graphs.py Outdated
<div class="destructive-confirmation" data-confirmation-id="${confirmationId}">
<div class="confirmation-text">${step.message.replace(/\n/g, '<br>')}</div>
<div class="confirmation-buttons">
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This does not escape backslash characters in the input.

Copilot Autofix

AI about 1 year ago

To fix the problem, we need to ensure that both backslashes and single quotes are properly escaped in the string before embedding it in the onclick attribute. The best way to do this is to first replace all backslashes (\) with double backslashes (\\), and then replace all single quotes (') with escaped single quotes (\'). This order is important to avoid double-escaping. The fix should be applied in the construction of the confirmationHTML string in the addDestructiveConfirmationMessage function, specifically on lines 228 and 231. No new dependencies are required, as this can be done with standard JavaScript string methods.


Suggested changeset 1
api/static/js/modules/chat.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/api/static/js/modules/chat.js b/api/static/js/modules/chat.js
--- a/api/static/js/modules/chat.js
+++ b/api/static/js/modules/chat.js
@@ -227,6 +227,6 @@
             <div class="confirmation-buttons">
-                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${confirmationId}')">
                     CONFIRM - Execute Query
                 </button>
-                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${confirmationId}')">
                     CANCEL - Abort Operation
EOF
@@ -227,6 +227,6 @@
<div class="confirmation-buttons">
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${confirmationId}')">
CONFIRM - Execute Query
</button>
<button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
<button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}', '${confirmationId}')">
CANCEL - Abort Operation
Copilot is powered by AI and may make mistakes. Always verify output.
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
CONFIRM - Execute Query
</button>
<button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This does not escape backslash characters in the input.

Copilot Autofix

AI about 1 year ago

To fix the problem, we need to ensure that both backslashes and single quotes are properly escaped in the string that is embedded in the onclick attribute. The best way to do this is to first escape all backslashes (\\\), then escape all single quotes ('\'). This order is important to avoid double-escaping. The fix should be applied to both instances where step.sql_query.replace(/'/g, "\\'") is used (lines 228 and 231). We can define a helper function within the file to perform this escaping, or use a well-known library if available, but since we are limited to the shown code, a helper function is appropriate. The helper function should be defined above its usage, and both lines should be updated to use it.

Suggested changeset 1
api/static/js/modules/chat.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/api/static/js/modules/chat.js b/api/static/js/modules/chat.js
--- a/api/static/js/modules/chat.js
+++ b/api/static/js/modules/chat.js
@@ -7,2 +7,8 @@
 
+
+// Helper function to escape backslashes and single quotes for JS string literals
+function escapeForJsString(str) {
+    return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
+}
+
 export async function sendMessage() {
@@ -227,6 +233,6 @@
             <div class="confirmation-buttons">
-                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${escapeForJsString(step.sql_query)}', '${confirmationId}')">
                     CONFIRM - Execute Query
                 </button>
-                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${escapeForJsString(step.sql_query)}', '${confirmationId}')">
                     CANCEL - Abort Operation
EOF
@@ -7,2 +7,8 @@


// Helper function to escape backslashes and single quotes for JS string literals
function escapeForJsString(str) {
return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}

export async function sendMessage() {
@@ -227,6 +233,6 @@
<div class="confirmation-buttons">
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
<button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${escapeForJsString(step.sql_query)}', '${confirmationId}')">
CONFIRM - Execute Query
</button>
<button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
<button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${escapeForJsString(step.sql_query)}', '${confirmationId}')">
CANCEL - Abort Operation
Copilot is powered by AI and may make mistakes. Always verify output.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
api/static/js/modules/chat.js (1)

213-247: Security: Incomplete string escaping in onclick handlers.

The SQL query escaping only handles single quotes but not backslashes, which could lead to JavaScript injection vulnerabilities.

Apply this fix to properly escape both backslashes and quotes:

-                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="confirm-btn danger" onclick="handleDestructiveConfirmation('CONFIRM', '${step.sql_query.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', '${confirmationId}')">
                     CONFIRM - Execute Query
                 </button>
-                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/'/g, "\\'")}', '${confirmationId}')">
+                <button class="cancel-btn" onclick="handleDestructiveConfirmation('CANCEL', '${step.sql_query.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', '${confirmationId}')">
                     CANCEL - Abort Operation
                 </button>

Consider using data attributes and event delegation instead of inline onclick handlers for better security:

// Alternative approach using data attributes
const confirmationHTML = `
    <div class="destructive-confirmation" data-confirmation-id="${confirmationId}">
        <div class="confirmation-text">${step.message.replace(/\n/g, '<br>')}</div>
        <div class="confirmation-buttons">
            <button class="confirm-btn danger" data-action="CONFIRM" data-sql="${step.sql_query}" data-confirmation-id="${confirmationId}">
                CONFIRM - Execute Query
            </button>
            <button class="cancel-btn" data-action="CANCEL" data-sql="${step.sql_query}" data-confirmation-id="${confirmationId}">
                CANCEL - Abort Operation
            </button>
        </div>
    </div>
`;

// Then use event delegation
document.addEventListener('click', (e) => {
    if (e.target.matches('.confirm-btn, .cancel-btn')) {
        const action = e.target.dataset.action;
        const sqlQuery = e.target.dataset.sql;
        const confirmationId = e.target.dataset.confirmationId;
        handleDestructiveConfirmation(action, sqlQuery, confirmationId);
    }
});
🧹 Nitpick comments (6)
api/static/js/modules/config.js (1)

8-31: Well-organized selector definitions.

The selector object provides a clean, centralized way to manage CSS selectors. Consider whether both SELECTORS and DOM objects are necessary, as they serve similar purposes.

api/static/js/modules/modals.js (1)

69-114: Extract loading state management to reduce duplication.

The loading state management code is repeated in multiple places. Consider extracting it into helper functions for better maintainability.

+        function setLoadingState(isLoading) {
+            const connectText = connectPgModalBtn.querySelector('.pg-modal-connect-text');
+            const loadingSpinner = connectPgModalBtn.querySelector('.pg-modal-loading-spinner');
+            const cancelBtn = document.getElementById('pg-modal-cancel');
+            
+            connectText.style.display = isLoading ? 'none' : 'inline';
+            loadingSpinner.style.display = isLoading ? 'flex' : 'none';
+            connectPgModalBtn.disabled = isLoading;
+            cancelBtn.disabled = isLoading;
+            pgUrlInput.disabled = isLoading;
+        }

        connectPgModalBtn.addEventListener('click', function() {
            // ... validation code ...
            
-            // Show loading state
-            const connectText = connectPgModalBtn.querySelector('.pg-modal-connect-text');
-            const loadingSpinner = connectPgModalBtn.querySelector('.pg-modal-loading-spinner');
-            const cancelBtn = document.getElementById('pg-modal-cancel');
-            
-            connectText.style.display = 'none';
-            loadingSpinner.style.display = 'flex';
-            connectPgModalBtn.disabled = true;
-            cancelBtn.disabled = true;
-            pgUrlInput.disabled = true;
+            setLoadingState(true);
            
            fetch('/database', {
                // ... fetch code ...
            })
            .then(data => {
-                // Reset loading state
-                connectText.style.display = 'inline';
-                loadingSpinner.style.display = 'none';
-                connectPgModalBtn.disabled = false;
-                cancelBtn.disabled = false;
-                pgUrlInput.disabled = false;
+                setLoadingState(false);
                
                // ... success handling ...
            })
            .catch(error => {
-                // Reset loading state on error
-                connectText.style.display = 'inline';
-                loadingSpinner.style.display = 'none';
-                connectPgModalBtn.disabled = false;
-                cancelBtn.disabled = false;
-                pgUrlInput.disabled = false;
+                setLoadingState(false);
                
                // ... error handling ...
            });
        });
api/static/js/modules/graphs.js (1)

8-93: Well-structured graph loading with comprehensive error handling.

The function properly handles authentication, error states, and UI updates. The authentication check is consistent with the pattern used in modals.js.

Consider extracting the UI state management into helper functions to improve readability:

+function setInputState(disabled, placeholder) {
+    DOM.messageInput.disabled = disabled;
+    DOM.submitButton.disabled = disabled;
+    DOM.messageInput.placeholder = placeholder;
+}

+function setGraphSelectOption(value, text, disabled = false) {
+    DOM.graphSelect.innerHTML = "";
+    const option = document.createElement("option");
+    option.value = value;
+    option.textContent = text;
+    option.disabled = disabled;
+    DOM.graphSelect.appendChild(option);
+}

export function loadGraphs() {
    const isAuthenticated = window.isAuthenticated !== undefined ? window.isAuthenticated : false;
    
    if (!isAuthenticated) {
-        DOM.graphSelect.innerHTML = "";
-        const option = document.createElement("option");
-        option.value = "";
-        option.textContent = "Please log in to access graphs";
-        option.disabled = true;
-        DOM.graphSelect.appendChild(option);
-        
-        DOM.messageInput.disabled = true;
-        DOM.submitButton.disabled = true;
-        DOM.messageInput.placeholder = "Please log in to start chatting";
+        setGraphSelectOption("", "Please log in to access graphs", true);
+        setInputState(true, "Please log in to start chatting");
        return;
    }
    // ... rest of function with similar refactoring
}
api/static/js/modules/messages.js (2)

7-68: Complex but well-structured message handling.

The function handles multiple message types and user avatars appropriately. Consider breaking this into smaller, focused functions for better maintainability.

Consider extracting message type handling:

+function createMessageElements(message, isUser, isFollowup, isFinalResult, isLoading, userInfo) {
+    const messageDiv = document.createElement('div');
+    const messageDivContainer = document.createElement('div');
+    
+    messageDiv.className = "message";
+    messageDivContainer.className = "message-container";
+    
+    // Handle message type styling and state updates
+    if (isFollowup) {
+        messageDivContainer.className += " followup-message-container";
+        messageDiv.className += " followup-message";
+    } else if (isUser) {
+        // ... user message handling
+    } // ... etc
+    
+    return { messageDiv, messageDivContainer };
+}

export function addMessage(message, isUser = false, isFollowup = false, isFinalResult = false, isLoading = false, userInfo = null) {
-    const messageDiv = document.createElement('div');
-    const messageDivContainer = document.createElement('div');
-    // ... complex logic ...
+    const { messageDiv, messageDivContainer } = createMessageElements(message, isUser, isFollowup, isFinalResult, isLoading, userInfo);
    
    // ... rest of the function
}

87-125: Improve format detection robustness and browser compatibility.

The formatting logic handles the main cases well, but consider these improvements:

  1. Browser compatibility: replaceAll might not be available in older browsers:
-            part = part.replaceAll(']', '');
+            part = part.replace(/]/g, '');
  1. More robust SQL detection:
-    if (text.startsWith('```sql') && text.endsWith('```')) {
+    if (/^```sql\n[\s\S]*```$/.test(text)) {
  1. Safer array detection:
-    if (text.includes('[') && text.includes(']')) {
+    if (/\[.*\]/.test(text) && !text.includes('```')) {
api/static/js/modules/chat.js (1)

301-302: Consider avoiding global scope pollution.

Making functions globally available is necessary for inline onclick handlers but pollutes the global scope. The event delegation approach suggested earlier would eliminate this need.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cfc80be and 5ccb46c.

📒 Files selected for processing (16)
  • api/static/css/main.css (1 hunks)
  • api/static/css/modules/confirmations.css (1 hunks)
  • api/static/css/modules/messages.css (1 hunks)
  • api/static/css/modules/modals.css (1 hunks)
  • api/static/css/modules/toolbar.css (1 hunks)
  • api/static/css/modules/utilities.css (1 hunks)
  • api/static/css/modules/variables.css (1 hunks)
  • api/static/js/app.js (1 hunks)
  • api/static/js/modules/chat.js (1 hunks)
  • api/static/js/modules/config.js (1 hunks)
  • api/static/js/modules/graphs.js (1 hunks)
  • api/static/js/modules/messages.js (1 hunks)
  • api/static/js/modules/modals.js (1 hunks)
  • api/static/js/modules/ui.js (1 hunks)
  • api/templates/base.j2 (1 hunks)
  • api/templates/components/scripts.j2 (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • api/static/css/main.css
  • api/static/css/modules/confirmations.css
  • api/static/css/modules/messages.css
  • api/static/css/modules/utilities.css
  • api/static/css/modules/variables.css
  • api/static/css/modules/modals.css
  • api/static/css/modules/toolbar.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/templates/components/scripts.j2
  • api/templates/base.j2
🧰 Additional context used
🧬 Code Graph Analysis (1)
api/static/js/modules/modals.js (1)
api/static/js/modules/graphs.js (1)
  • isAuthenticated (10-10)
🪛 GitHub Check: CodeQL
api/static/js/modules/chat.js

[failure] 228-228: Incomplete string escaping or encoding
This does not escape backslash characters in the input.


[failure] 231-231: Incomplete string escaping or encoding
This does not escape backslash characters in the input.

🔇 Additional comments (19)
api/static/js/modules/config.js (2)

5-6: LGTM!

The message delimiter is appropriately unique and unlikely to conflict with actual data content.


58-66: LGTM!

The application state structure is well-designed for managing chat history and request control. URLSearchParams usage is appropriate for URL parameter handling.

api/static/js/app.js (4)

20-33: LGTM!

The initialization sequence is well-structured with logical ordering: chat initialization, event listeners, UI components, then data loading.


35-74: Add defensive checks for DOM elements.

The event listener setup is comprehensive and well-organized. However, consider adding null checks before attaching listeners to prevent runtime errors if DOM elements are missing.

Example defensive approach:

function setupEventListeners() {
    // Chat functionality
-    DOM.submitButton.addEventListener('click', sendMessage);
+    DOM.submitButton?.addEventListener('click', sendMessage);
-    DOM.pauseButton.addEventListener('click', pauseRequest);
+    DOM.pauseButton?.addEventListener('click', pauseRequest);
    // ... continue for other elements
}

76-81: LGTM!

Clean delegation to specialized setup functions from other modules maintains good separation of concerns.


83-88: LGTM!

Proper use of DOMContentLoaded ensures the DOM is ready before initialization. The data loading function is appropriately focused.

api/static/js/modules/graphs.js (1)

115-117: LGTM!

Simple and appropriate functionality - resetting chat context when graph selection changes is the correct behavior.

api/static/js/modules/messages.js (2)

70-85: LGTM!

Clean utility functions for loading message management with appropriate DOM manipulation and error handling.


127-143: LGTM!

Clean chat initialization with appropriate state reset and contextual greeting messages based on graph availability.

api/static/js/modules/chat.js (5)

1-7: Clean module structure with well-organized imports.

The modular approach with separate config and messages modules promotes good separation of concerns.


8-76: Well-implemented async message handling with proper request lifecycle management.

Good use of AbortController for cancellable requests and comprehensive error handling that distinguishes between user aborts and actual errors.


78-119: Robust streaming response handler with proper chunk processing.

Excellent implementation of streaming response processing with appropriate buffering and delimiter-based message parsing.


121-144: Clean message routing pattern with appropriate type handling.

The switch-like routing to dedicated handlers for each message type promotes maintainability and extensibility.


249-299: Well-structured confirmation handler with proper state management.

Good use of unique IDs to target specific confirmation dialogs and comprehensive error handling for the server request.

api/static/js/modules/ui.js (5)

1-30: Well-implemented responsive menu toggle with proper mobile handling.

Good separation of mobile and desktop behaviors, with appropriate padding adjustments only for desktop views.


32-50: Smart use of dynamic imports to avoid circular dependencies.

The dynamic import of initChat is an elegant solution to prevent circular dependency issues between modules.


52-82: Complete dropdown implementation with excellent UX patterns.

Includes all essential dropdown behaviors: toggle on click, close on outside click, keyboard accessibility with Escape key, and proper event propagation handling.


84-130: Excellent theme management with persistence and intuitive cycling.

Well-implemented theme toggle with localStorage persistence, sensible cycling order (dark → light → system), and dynamic button titles for better UX.


132-150: Comprehensive resize handler maintaining UI consistency.

Handles all combinations of menu state and viewport size to ensure consistent padding and layout across responsive breakpoints.

Comment on lines +33 to +56
// Get DOM elements
export const DOM = {
messageInput: document.getElementById('message-input'),
submitButton: document.getElementById('submit-button'),
pauseButton: document.getElementById('pause-button'),
newChatButton: document.getElementById('reset-button'),
chatMessages: document.getElementById('chat-messages'),
expValue: document.getElementById('exp-value'),
confValue: document.getElementById('conf-value'),
missValue: document.getElementById('info-value'),
ambValue: document.getElementById('amb-value'),
fileUpload: document.getElementById('schema-upload'),
fileLabel: document.getElementById('custom-file-upload'),
sideMenuButton: document.getElementById('side-menu-button'),
menuButton: document.getElementById('menu-button'),
menuContainer: document.getElementById('menu-container'),
chatContainer: document.getElementById('chat-container'),
expInstructions: document.getElementById('instructions-textarea'),
inputContainer: document.getElementById('input-container'),
graphSelect: document.getElementById('graph-select'),
resetConfirmationModal: document.getElementById('reset-confirmation-modal'),
resetConfirmBtn: document.getElementById('reset-confirm-btn'),
resetCancelBtn: document.getElementById('reset-cancel-btn')
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checks for DOM element references.

The DOM elements are resolved at module load time without null checks. If any elements don't exist or the module loads before DOM is ready, this could cause runtime errors.

Consider adding null checks or lazy initialization:

// Get DOM elements
export const DOM = {
-    messageInput: document.getElementById('message-input'),
+    messageInput: document.getElementById('message-input') || null,
-    submitButton: document.getElementById('submit-button'),
+    submitButton: document.getElementById('submit-button') || null,
    // ... continue for all elements
};

Or use a getter-based approach for lazy initialization:

export const DOM = {
-    messageInput: document.getElementById('message-input'),
+    get messageInput() { return document.getElementById('message-input'); },
    // ... continue for all elements
};

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In api/static/js/modules/config.js between lines 33 and 56, the DOM elements are
assigned directly at module load time without checking if the elements exist,
which can cause runtime errors if the DOM is not ready or elements are missing.
To fix this, add null checks after each document.getElementById call to verify
the element exists before using it, or refactor the DOM object to use getter
functions that lazily retrieve the elements when accessed, ensuring the DOM is
fully loaded and elements are present.

Comment on lines +95 to +113
export function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;

const formData = new FormData();
formData.append('file', file);

fetch("/graphs", {
method: 'POST',
body: formData,
}).then(response => {
response.json()
}).then(data => {
console.log('File uploaded successfully', data);
}).catch(error => {
console.error('Error uploading file:', error);
addMessage('Sorry, there was an error uploading your file: ' + error.message, false);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve user feedback and refresh graph list after upload.

The file upload function should provide user feedback for successful uploads and refresh the graph list to show the newly uploaded schema.

export function handleFileUpload(event) {
    const file = event.target.files[0];
    if (!file) return;

    const formData = new FormData();
    formData.append('file', file);

    fetch("/graphs", {
        method: 'POST',
        body: formData,
    }).then(response => {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        return response.json();
    }).then(data => {
        console.log('File uploaded successfully', data);
+        addMessage('Schema uploaded successfully!', false);
+        // Refresh the graph list to show the new schema
+        loadGraphs();
    }).catch(error => {
        console.error('Error uploading file:', error);
        addMessage('Sorry, there was an error uploading your file: ' + error.message, false);
    });
}
📝 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
export function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
fetch("/graphs", {
method: 'POST',
body: formData,
}).then(response => {
response.json()
}).then(data => {
console.log('File uploaded successfully', data);
}).catch(error => {
console.error('Error uploading file:', error);
addMessage('Sorry, there was an error uploading your file: ' + error.message, false);
});
}
export function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
fetch("/graphs", {
method: 'POST',
body: formData,
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}).then(data => {
console.log('File uploaded successfully', data);
addMessage('Schema uploaded successfully!', false);
// Refresh the graph list to show the new schema
loadGraphs();
}).catch(error => {
console.error('Error uploading file:', error);
addMessage('Sorry, there was an error uploading your file: ' + error.message, false);
});
}
🤖 Prompt for AI Agents
In api/static/js/modules/graphs.js between lines 95 and 113, the
handleFileUpload function lacks user feedback on successful uploads and does not
refresh the graph list. Modify the function to add a success message to the user
interface after a successful upload and call the existing function that
refreshes the graph list to display the newly uploaded schema. Ensure these
updates happen after confirming the upload response is successful.

Comment on lines +105 to +107
}).then(response => {
response.json()
}).then(data => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incomplete promise chain.

The promise chain is missing a return statement for response.json(), which will cause the subsequent .then() block to receive undefined instead of the parsed JSON data.

    fetch("/graphs", {
        method: 'POST',
        body: formData,
-    }).then(response => {
-        response.json()
+    }).then(response => {
+        if (!response.ok) {
+            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+        }
+        return response.json();
    }).then(data => {
📝 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
}).then(response => {
response.json()
}).then(data => {
fetch("/graphs", {
method: 'POST',
body: formData,
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}).then(data => {
🤖 Prompt for AI Agents
In api/static/js/modules/graphs.js around lines 105 to 107, the promise chain is
missing a return statement for response.json(), causing the next then block to
receive undefined. Fix this by adding a return before response.json() so the
parsed JSON data is correctly passed to the following then handler.

Comment on lines +5 to +19
export function setupAuthenticationModal() {
var isAuthenticated = window.isAuthenticated !== undefined ? window.isAuthenticated : false;
var googleLoginModal = document.getElementById('google-login-modal');
var container = document.getElementById('container');

if (googleLoginModal && container) {
if (!isAuthenticated) {
googleLoginModal.style.display = 'flex';
container.style.filter = 'blur(2px)';
} else {
googleLoginModal.style.display = 'none';
container.style.filter = '';
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checks for DOM elements.

The authentication modal logic is sound and consistent with the pattern used in graphs.js. However, add defensive checks to prevent runtime errors if DOM elements are missing.

export function setupAuthenticationModal() {
    var isAuthenticated = window.isAuthenticated !== undefined ? window.isAuthenticated : false;
    var googleLoginModal = document.getElementById('google-login-modal');
    var container = document.getElementById('container');
    
-    if (googleLoginModal && container) {
+    if (!googleLoginModal || !container) {
+        console.warn('Authentication modal elements not found');
+        return;
+    }
+    
-        if (!isAuthenticated) {
+    if (!isAuthenticated) {
            googleLoginModal.style.display = 'flex';
            container.style.filter = 'blur(2px)';
-        } else {
+    } else {
            googleLoginModal.style.display = 'none';
            container.style.filter = '';
-        }
-    }
+    }
}
📝 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
export function setupAuthenticationModal() {
var isAuthenticated = window.isAuthenticated !== undefined ? window.isAuthenticated : false;
var googleLoginModal = document.getElementById('google-login-modal');
var container = document.getElementById('container');
if (googleLoginModal && container) {
if (!isAuthenticated) {
googleLoginModal.style.display = 'flex';
container.style.filter = 'blur(2px)';
} else {
googleLoginModal.style.display = 'none';
container.style.filter = '';
}
}
}
export function setupAuthenticationModal() {
var isAuthenticated = window.isAuthenticated !== undefined ? window.isAuthenticated : false;
var googleLoginModal = document.getElementById('google-login-modal');
var container = document.getElementById('container');
if (!googleLoginModal || !container) {
console.warn('Authentication modal elements not found');
return;
}
if (!isAuthenticated) {
googleLoginModal.style.display = 'flex';
container.style.filter = 'blur(2px)';
} else {
googleLoginModal.style.display = 'none';
container.style.filter = '';
}
}
🤖 Prompt for AI Agents
In api/static/js/modules/modals.js between lines 5 and 19, add explicit null
checks for the DOM elements googleLoginModal and container before accessing
their style properties to prevent runtime errors if these elements are not
found. Wrap the style manipulation code inside conditionals that verify these
elements are not null or undefined.

Comment on lines +21 to +51
export function setupPostgresModal() {
var pgModal = document.getElementById('pg-modal');
var openPgModalBtn = document.getElementById('open-pg-modal');
var cancelPgModalBtn = document.getElementById('pg-modal-cancel');
var connectPgModalBtn = document.getElementById('pg-modal-connect');
var pgUrlInput = document.getElementById('pg-url-input');

if (openPgModalBtn && pgModal) {
openPgModalBtn.addEventListener('click', function() {
pgModal.style.display = 'flex';
// Focus the input field when modal opens
if (pgUrlInput) {
setTimeout(() => {
pgUrlInput.focus();
}, 100);
}
});
}

if (cancelPgModalBtn && pgModal) {
cancelPgModalBtn.addEventListener('click', function() {
pgModal.style.display = 'none';
});
}

// Allow closing Postgres modal with Escape key
document.addEventListener('keydown', function(e) {
if (pgModal && pgModal.style.display === 'flex' && e.key === 'Escape') {
pgModal.style.display = 'none';
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null checks and improve error handling.

The modal setup logic is well-structured with proper event handling. However, add defensive checks for DOM elements to prevent runtime errors.

export function setupPostgresModal() {
    var pgModal = document.getElementById('pg-modal');
    var openPgModalBtn = document.getElementById('open-pg-modal');
    var cancelPgModalBtn = document.getElementById('pg-modal-cancel');
    var connectPgModalBtn = document.getElementById('pg-modal-connect');
    var pgUrlInput = document.getElementById('pg-url-input');
    
+    // Early return if required elements are missing
+    if (!pgModal) {
+        console.warn('Postgres modal elements not found');
+        return;
+    }
    
-    if (openPgModalBtn && pgModal) {
+    if (openPgModalBtn) {
        // ... rest of function
🤖 Prompt for AI Agents
In api/static/js/modules/modals.js around lines 21 to 51, add null checks before
accessing or manipulating DOM elements like pgModal, openPgModalBtn,
cancelPgModalBtn, connectPgModalBtn, and pgUrlInput to prevent runtime errors.
Wrap event listener attachments and style changes in conditional checks
verifying these elements exist. This defensive coding ensures the modal setup
does not fail if any expected element is missing.

Comment thread api/static/js/modules/modals.js Outdated
Comment on lines +105 to +107
// Reset loading state on error
connectText.style.display = 'inline';
loadingSpinner.style.display = 'none';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix missing return statement in promise chain.

The promise chain is missing a return statement, which could cause issues with error handling.

            fetch('/database', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ url: pgUrl })
            })
-            .then(response => response.json())
+            .then(response => {
+                if (!response.ok) {
+                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+                }
+                return response.json();
+            })
            .then(data => {
📝 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
// Reset loading state on error
connectText.style.display = 'inline';
loadingSpinner.style.display = 'none';
fetch('/database', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: pgUrl })
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
// ...
})
.catch(error => {
// Reset loading state on error
connectText.style.display = 'inline';
loadingSpinner.style.display = 'none';
// handle error
});
🤖 Prompt for AI Agents
In api/static/js/modules/modals.js around lines 105 to 107, the promise chain
lacks a return statement after handling the error, which can disrupt proper
error propagation. Add a return statement before the promise or value in the
error handler to ensure the promise chain continues correctly and errors are
handled as expected.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
api/routes/graphs.py (1)

489-489: Log injection vulnerability still present.

The graph_id parameter is logged directly without sanitization, creating a log injection risk. This matches the static analysis warning and previous review comments.

Apply sanitization:

-        logging.error("Schema refresh failed for graph %s: %s", graph_id, message)
+        # Sanitize graph_id to prevent log injection  
+        sanitized_graph_id = graph_id.replace('\n', ' ').replace('\r', ' ')[:100]
+        logging.error("Schema refresh failed for graph %s: %s", sanitized_graph_id, message)
🧹 Nitpick comments (2)
api/routes/graphs.py (2)

65-75: Clean up commented code or complete the implementation.

The commented XML and CSV direct payload handling is incomplete. Either remove these commented sections or complete the implementation with proper graph_id extraction.

-    # # ✅ Handle XML Payload
-    # elif content_type.startswith("application/xml") or content_type.startswith("text/xml"):
-    #     xml_data = request.data
-    #     graph_id = ""
-    #     success, result = ODataLoader.load(graph_id, xml_data)
-
-    # # ✅ Handle CSV Payload
-    # elif content_type.startswith("text/csv"):
-    #     csv_data = request.data
-    #     graph_id = ""
-    #     success, result = CSVLoader.load(graph_id, csv_data)

121-351: Consider breaking down this complex function.

The function has high complexity (230+ lines, multiple responsibilities) as noted by pylint warnings. While it works correctly, consider refactoring into smaller functions:

  • Extract SQL execution logic
  • Extract streaming response generation
  • Extract destructive operation detection
  • Extract schema refresh handling

This would improve maintainability, testability, and readability.

Example structure:

def query_graph(graph_id: str):
    # Validation and setup
    validated_data = _validate_query_request(graph_id, request.get_json())
    return Response(stream_with_context(_generate_query_response(validated_data)), 
                   content_type="application/json")

def _generate_query_response(data):
    # Main generator logic broken into smaller pieces
    pass
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 890228a and 1002d36.

📒 Files selected for processing (4)
  • api/routes/graphs.py (1 hunks)
  • api/static/js/chat.js (3 hunks)
  • api/templates/components/toolbar.j2 (1 hunks)
  • api/templates/components/user_profile.j2 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/templates/components/user_profile.j2
  • api/templates/components/toolbar.j2
  • api/static/js/chat.js
🧰 Additional context used
🪛 GitHub Check: CodeQL
api/routes/graphs.py

[failure] 489-489: Log Injection
This log entry depends on a user-provided value.

🪛 GitHub Actions: Pylint
api/routes/graphs.py

[warning] 44-495: pylint: Too many return statements (8/6), too many local variables (26/15), too many branches (17/12), too many statements (up to 87/50), broad exception catching, and trailing whitespace

🔇 Additional comments (8)
api/routes/graphs.py (8)

1-22: LGTM on imports and blueprint setup.

The imports are well-organized and the blueprint configuration is appropriate for the graph management functionality.


24-26: Good security practice for log sanitization.

This helper function effectively prevents log injection by removing newlines and limiting query length - addressing the security concerns from previous reviews.


28-39: LGTM on graph listing with proper user isolation.

The route correctly filters graphs by user ID and strips the prefix from responses, ensuring proper user isolation and clean API responses.


116-118: Good improvement in error handling.

The error handling now properly logs detailed errors internally while returning generic messages to users, addressing the information exposure concerns from previous reviews.


121-151: Input validation and sanitization improvements look good.

The function properly validates graph_id, sanitizes it to prevent injection, and validates the request data structure. Good security practices implemented.


152-152: Good use of query sanitization.

Using the sanitize_query helper function for logging addresses the log injection vulnerabilities identified in previous reviews.


354-457: Well-implemented confirmation workflow.

The destructive operation confirmation logic is clear and follows good security practices with proper error handling and generic error messages.


460-500: Clean implementation of manual schema refresh.

The route provides good error handling and appropriate responses for manual schema refresh operations, aside from the log injection issue noted above.

@gkorland
gkorland merged commit d9a55da into main Aug 2, 2025
6 of 10 checks passed
@gkorland
gkorland deleted the refactor branch August 2, 2025 12:27
@coderabbitai coderabbitai Bot mentioned this pull request Aug 30, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Nov 23, 2025
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