Skip to content

Conversation

@avirajsingh7
Copy link
Collaborator

@avirajsingh7 avirajsingh7 commented Aug 8, 2025

Summary

Target issue is #253 and #254
Explain the motivation for making this change. What existing problem does the pull request solve?

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • Chores
    • Standardized structured logging across user, credential, provider, and utility flows with richer contextual details and severity levels; missing OpenAI credential warnings upgraded to errors.
  • New Features
    • Credential validation and encryption before storage.
    • New credential retrieval helpers for org/project/provider contexts.
  • Bug Fixes
    • Hardened credential create/update/remove flows with improved validation and clearer error reporting.

@avirajsingh7 avirajsingh7 self-assigned this Aug 8, 2025
@avirajsingh7 avirajsingh7 added the enhancement New feature or request label Aug 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 8, 2025

Walkthrough

Adds module-level loggers and logging calls across API routes, core utils, provider validation, and CRUD layers; credentials CRUD now validates providers/fields, encrypts credentials before storage, adds retrieval helpers, and updates set_creds_for_org to return List[Credential]. No control-flow or exception-type changes.

Changes

Cohort / File(s) Change Summary
Credentials API
backend/app/api/routes/credentials.py
Add module logger; emit contextual warning/error logs at create/read/update/delete failure points; no control-flow changes.
User API
backend/app/api/routes/users.py
Add module logger; log errors before raising HTTP exceptions and emit info logs on successful updates/password changes/deletions.
Provider validation
backend/app/core/providers.py
Add module logger; log errors when provider unsupported or required credential fields missing before raising ValueError.
Core utilities
backend/app/core/util.py
Replace warnings.warn with logger.warning/logger.error; add module logger; semantics otherwise unchanged.
Credential CRUD (validation/encryption/logging)
backend/app/crud/credentials.py
Add module logger; validate providers/fields, encrypt credentials before persisting, handle IntegrityError with rollback and detailed error logs; add retrieval helpers (get_key_by_org, get_creds_by_org, get_provider_credential, get_providers); update set_creds_for_org return type to List[Credential].
User CRUD
backend/app/crud/user.py
Add module logger; log info after user creation and updates (user id and updated fields).
OpenAI client util
backend/app/utils.py
Change missing-OpenAI-credentials message level from warning to error; logic unchanged.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant API as "API Route\n(handlers)"
    participant Core as "CRUD / Core\n(validators, encrypt)"
    participant DB as "Database"
    participant Log as "Logger"

    Client->>API: request (create/update/read/delete)
    API->>Core: validate input, check org/project, call provider validators
    alt validation fails
        Core->>Log: error (provider/fields/org/project)
        Core-->>API: raise error
        API-->>Client: HTTP error
    else validation passes
        Core->>Core: encrypt credentials (if applicable)
        Core->>DB: persist (insert/update/delete)
        alt DB error (IntegrityError)
            DB->>Core: error
            Core->>DB: rollback
            Core->>Log: error (org/project/provider, exc)
            Core-->>API: raise error
            API-->>Client: HTTP error
        else success
            DB-->>Core: success
            Core->>Log: info (created/updated/removed with ids)
            Core-->>API: result
            API-->>Client: 200/201 + payload
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • AkhileshNegi
  • vijay-T4D
  • nishika26

Poem

🐇
I hopped through logs and hid each key,
I whispered warnings where bugs might be.
I wrapped secrets in ciphered thread,
Then danced when rows were safely fed.
Hop, patch, release — a bunny's glee!


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 00be723 and 03fe0cc.

📒 Files selected for processing (1)
  • backend/app/api/routes/users.py (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/api/routes/users.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: checks (3.11.7, 6)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch logs/creds_user_module

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit 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

Status, Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (2)
backend/app/utils.py (1)

191-198: Avoid leaking internal exception details in HTTP 500 response

Returning str(e) to clients can leak sensitive info. Log the full exception (with stack), but return a generic message.

-        logger.error(
-            f"[get_openai_client] Failed to configure OpenAI client. | project_id: {project_id} | error: {str(e)}",
-            exc_info=True,
-        )
-        raise HTTPException(
-            status_code=500,
-            detail=f"Failed to configure OpenAI client: {str(e)}",
-        )
+        logger.error(
+            "[get_openai_client] Failed to configure OpenAI client. | project_id=%s",
+            project_id,
+            exc_info=True,
+        )
+        raise HTTPException(
+            status_code=500,
+            detail="Failed to configure OpenAI client.",
+        )
backend/app/core/util.py (1)

42-51: Fix type hints: return Optional client on failure

Both functions return (None, False) on failure, which doesn’t match the current signature.

-def configure_langfuse(credentials: dict) -> tuple[Langfuse, bool]:
+def configure_langfuse(credentials: dict) -> tuple[Langfuse | None, bool]:
-def configure_openai(credentials: dict) -> tuple[OpenAI, bool]:
+def configure_openai(credentials: dict) -> tuple[OpenAI | None, bool]:

Also applies to: 76-85

🧹 Nitpick comments (13)
backend/app/utils.py (1)

180-182: Recalibrate log level and use parameterized logging to avoid string interpolation overhead

This is a client/config error returning 400. Consider logging at warning (not error) and use parameterized logging with contextual fields.

-        logger.error(
-            f"[get_openai_client] OpenAI credentials not found. | project_id: {project_id}"
-        )
+        logger.warning(
+            "[get_openai_client] OpenAI credentials not found. | org_id=%s | project_id=%s",
+            org_id,
+            project_id,
+        )
backend/app/core/util.py (2)

20-26: Include traceback and parameterized logging for unknown exception handler

Add exc_info and avoid eager f-string formatting. Consider keeping client detail generic to prevent info leaks.

-    logger.warning(
-        'Unexpected exception "{}": {}'.format(
-            type(error).__name__,
-            error,
-        )
-    )
+    logger.warning(
+        'Unexpected exception "%s": %s',
+        type(error).__name__,
+        error,
+        exc_info=True,
+    )
-    raise HTTPException(status_code=status_code, detail=str(error))
+    raise HTTPException(status_code=status_code, detail="Unexpected error")

72-73: Log with traceback for client configuration errors

Include stack traces for easier diagnosis and avoid f-strings in logging.

-        logger.error(f"Failed to configure Langfuse: {str(e)}")
+        logger.error("Failed to configure Langfuse: %s", e, exc_info=True)
-        logger.error(f"Failed to configure OpenAI client: {str(e)}")
+        logger.error("Failed to configure OpenAI client: %s", e, exc_info=True)

Also applies to: 94-95

backend/app/core/providers.py (1)

52-54: Log level and parameterization for validation errors

These are client-side validation failures; warning is more appropriate than error. Also use parameterized logging.

-        logger.error(
-            f"[validate_provider] Unsupported provider: {provider}. Supported providers are: {supported}"
-        )
+        logger.warning(
+            "[validate_provider] Unsupported provider: %s | supported=%s",
+            provider,
+            supported,
+        )
-        logger.error(
-            f"[validate_provider_credentials] Missing required fields for {provider}: {', '.join(missing_fields)}"
-        )
+        logger.warning(
+            "[validate_provider_credentials] Missing required fields | provider=%s | missing=%s",
+            provider,
+            ", ".join(missing_fields),
+        )

Also applies to: 76-78

backend/app/api/routes/users.py (1)

188-188: Consider log level alignment for 404/403 cases

  • 404 not found (by id) often merits info/warning, not error (noise in error budgets).
  • 403 self-delete attempts is misuse → error is fine.
-        logger.error(f"[update_user_endpoint] User with id {user_id} not found")
+        logger.warning("[update_user_endpoint] User not found | user_id=%s", user_id)
-        logger.error(f"[delete_user] User with id {user_id} not found")
+        logger.warning("[delete_user] User not found | user_id=%s", user_id)

Keep:

logger.error("[delete_user] Attempt to delete self by superuser")

Also applies to: 217-217, 221-221

backend/app/api/routes/credentials.py (3)

1-1: Good: module-level logger added; consider structured, lazy logging

  • Keep the logger; it’s needed. Prefer structured logging (extra=...) and lazy formatting instead of f-strings to avoid eager interpolation and to improve log parsing.

Example:

-logger.error(f"[read_credential] No credentials found for organization {org_id}, project_id {project_id}")
+logger.error(
+  "[read_credential] No credentials found",
+  extra={"org_id": org_id, "project_id": project_id}
+)

Also applies to: 23-23


39-46: Project/org validation: truthiness check and log level

  • Use is not None for project_id to avoid false negatives if 0 is ever a valid ID.
  • This is a client error; consider logger.warning instead of logger.error to reduce error-noise for expected 4xx paths.
-# Validate project if provided
-if creds_in.project_id:
+// Validate project if provided
+if creds_in.project_id is not None:
   project = validate_project(session, creds_in.project_id)
   if project.organization_id != creds_in.organization_id:
-    logger.error(
+    logger.warning(
       f"[create_new_credential] Project {creds_in.project_id} does not belong to organization {creds_in.organization_id}"
     )

91-93: Use warning/info for expected 4xx outcomes to reduce error noise

  • Not-found and bad-input cases are expected control-flow in APIs. Consider logger.warning (or info if very common) instead of error for:
    • read_credential: none found
    • read_provider_credential: none found
    • update_credential: invalid input
    • delete_provider_credential: none found
    • delete_all_credentials: none found

Also applies to: 117-119, 135-135, 169-171, 195-197

backend/app/crud/credentials.py (5)

1-2: Modernize typing; align with Ruff hints (UP006/UP035)

  • Prefer built-in generics (list, dict) over typing.List/typing.Dict. Update return annotations accordingly.
-from typing import Optional, Dict, Any, List, Union
+from typing import Optional, Any, Union
...
-def set_creds_for_org(*, session: Session, creds_add: CredsCreate) -> List[Credential]:
+def set_creds_for_org(*, session: Session, creds_add: CredsCreate) -> list[Credential]:
...
-) -> Optional[Union[Dict[str, Any], Credential]]:
+) -> Optional[Union[dict[str, Any], Credential]]:
...
-def get_creds_by_org(... ) -> List[Credential]:
+def get_creds_by_org(... ) -> list[Credential]:
...
-def get_providers(... ) -> List[str]:
+def get_providers(... ) -> list[str]:
...
-def update_creds_for_org(... ) -> List[Credential]:
+def update_creds_for_org(... ) -> list[Credential]:
...
-def remove_creds_for_org(... ) -> List[Credential]:
+def remove_creds_for_org(... ) -> list[Credential]:

Also applies to: 18-18


23-26: CRUD layer should avoid HTTP concerns; raise domain errors

  • Logging is fine. Prefer raising a domain exception (e.g., ValueError or a custom CredentialsValidationError) here and let the API layer translate to HTTP. Currently this mixes HTTP in CRUD in some places and ValueError in others.

52-55: Prefer logger.exception and avoid duplicating exception text

  • logger.exception("msg") automatically includes stack trace. Avoid both str(e) in the message and exc_info=True which duplicates info.
-logger.error(
-    f"[set_creds_for_org] Integrity error while adding credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}, provider {provider}: {str(e)}",
-    exc_info=True,
-)
+logger.exception(
+    "[set_creds_for_org] Integrity error while adding credentials",
+    extra={"organization_id": creds_add.organization_id, "project_id": creds_add.project_id, "provider": provider},
+)

59-61: Consider atomicity for multi-provider create; enhance success log

  • If multiple providers are created in one call, current per-provider commit can leave partial state on failure. Consider wrapping the loop in a transaction so the operation is atomic.
  • Include count/providers in the success log for easier traceability.
-logger.info(
-    f"[set_creds_for_org] Successfully created credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}"
-)
+logger.info(
+    "[set_creds_for_org] Successfully created credentials",
+    extra={
+        "organization_id": creds_add.organization_id,
+        "project_id": creds_add.project_id,
+        "provider_count": len(created_credentials),
+        "providers": [c.provider for c in created_credentials],
+    },
+)

162-166: CRUD not-found: avoid raising HTTPException from data layer

  • Logging is good. For separation of concerns, return None or raise a domain-specific NotFound and let the API layer map it to HTTPException(404, ...). This keeps CRUD reusable outside HTTP contexts.
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9d371f7 and fa02f65.

📒 Files selected for processing (7)
  • backend/app/api/routes/credentials.py (11 hunks)
  • backend/app/api/routes/users.py (11 hunks)
  • backend/app/core/providers.py (3 hunks)
  • backend/app/core/util.py (4 hunks)
  • backend/app/crud/credentials.py (6 hunks)
  • backend/app/crud/user.py (3 hunks)
  • backend/app/utils.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
backend/app/api/routes/credentials.py (1)
backend/app/tests/api/routes/test_creds.py (4)
  • test_update_credentials (183-217)
  • test_set_credential (32-62)
  • test_update_credentials_failed_update (220-260)
  • test_set_credentials_for_invalid_project_org_relationship (65-87)
backend/app/crud/user.py (3)
backend/app/core/security.py (2)
  • get_password_hash (93-103)
  • verify_password (79-90)
backend/app/models/user.py (3)
  • User (48-60)
  • UserCreate (16-17)
  • UserUpdate (27-29)
backend/app/tests/crud/test_user.py (1)
  • test_update_user (79-91)
backend/app/core/providers.py (2)
backend/app/tests/core/test_providers.py (2)
  • test_validate_provider_credentials_missing_fields (17-31)
  • test_validate_provider_invalid (9-14)
backend/app/tests/crud/test_credentials.py (1)
  • test_invalid_provider (187-200)
🪛 Ruff (0.12.2)
backend/app/core/providers.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)


2-2: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)

backend/app/crud/credentials.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)


18-18: Use list instead of List for type annotation

Replace with list

(UP006)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: checks (3.11.7, 6)
🔇 Additional comments (2)
backend/app/core/util.py (1)

12-12: LGTM: module-level logger setup

Logger initialization looks good and aligns with the PR’s observability objective.

backend/app/crud/credentials.py (1)

174-176: No secrets are logged; only IDs and metadata are present

Verified that all logger calls matching api_key, secret, token, or password only output non-sensitive identifiers or status messages—no actual secret values are ever included.

Occurrences found:

  • backend/app/crud/api_key.py:77 – logs only api_key_id
  • backend/app/api/routes/users.py:113 – logs user email, not the password itself
  • backend/app/api/routes/api_keys.py:93 – logs only api_key_id

Info-level logging for successes and warnings for missing keys are appropriate and contain no decrypted credential material.

@codecov
Copy link

codecov bot commented Aug 8, 2025

Codecov Report

❌ Patch coverage is 78.26087% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/app/core/util.py 20.00% 4 Missing ⚠️
backend/app/api/routes/credentials.py 66.66% 3 Missing ⚠️
backend/app/crud/credentials.py 77.77% 2 Missing ⚠️
backend/app/utils.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (10)
backend/app/api/routes/users.py (10)

53-55: Lower severity for expected 400 and remove unnecessary f-string (Ruff F541)

Attempting to create a user with an existing email is a client error (400/409). Prefer warning over error. Also, f-string has no placeholders; Ruff flags this (F541).

Apply this diff:

-        logger.error(
-            f"[create_user_endpoint] Attempt to create user with existing email"
-        )
+        logger.warning(
+            "[create_user_endpoint] Attempt to create user with existing email"
+        )

Run this to ensure no remaining f-strings in logging calls across the repo:

#!/bin/bash
# Find f-strings used directly in logger calls
rg -nP -C2 '\blogger\.(debug|info|warning|error|exception|critical)\s*\(\s*f"' --glob '!**/node_modules/**' --glob '!**/dist/**'

82-82: Use warning and drop f-prefix (F541) for conflict case

Updating to an existing email results in 409 (expected client error). Also remove the extraneous f.

-            logger.error(f"[update_user_me] Attempt to update user with existing email")
+            logger.warning("[update_user_me] Attempt to update user with existing email")

91-91: Parameterize log arguments instead of f-strings

Prefer parameterized logging for consistency and to defer formatting.

-    logger.info(f"[update_user_me] User updated with id: {current_user.id}")
+    logger.info("[update_user_me] User updated | user_id=%s", current_user.id)

111-111: Parameterize log arguments

Same rationale as above; avoid f-strings in logger calls.

-    logger.info(f"[update_password_me] Password updated for user: {current_user.id}")
+    logger.info("[update_password_me] Password updated | user_id=%s", current_user.id)

129-129: Parameterize log arguments

Align with parameterized logging style.

-    logger.info(f"[delete_user_me] User deleted: {current_user.id}")
+    logger.info("[delete_user_me] User deleted | user_id=%s", current_user.id)

143-143: Lower severity for expected 400 and drop f-prefix (F541)

Registration conflict is a normal client error (400). Use warning and remove f-string.

-        logger.error(f"[register_user] Attempt to create user with existing email")
+        logger.warning("[register_user] Attempt to create user with existing email")

184-184: Use warning for 404 and parameterize

Not-found is an expected client error; prefer warning. Parameterize to avoid eager formatting.

-        logger.error(f"[update_user_endpoint] User with id {user_id} not found")
+        logger.warning("[update_user_endpoint] User not found | user_id=%s", user_id)

193-195: Use warning and remove f-prefix (F541) for conflict case

Email conflict on update is a client error. Use warning; remove f-string with no placeholders.

-            logger.error(
-                f"[update_user_endpoint] Attempt to update user with existing email"
-            )
+            logger.warning(
+                "[update_user_endpoint] Attempt to update user with existing email"
+            )

213-213: Use warning and parameterize for 404

Deleting a non-existent user is an expected condition; log as warning and parameterize.

-        logger.error(f"[delete_user] User with id {user_id} not found")
+        logger.warning("[delete_user] User not found | user_id=%s", user_id)

224-224: Parameterize log arguments

Switch to parameterized logging for consistency.

-    logger.info(f"[delete_user] User deleted: {user.id}")
+    logger.info("[delete_user] User deleted | user_id=%s", user.id)
🧹 Nitpick comments (2)
backend/app/api/routes/users.py (2)

123-123: Keep error severity; consider adding context

Privilege-violation attempt is correctly logged as error. Consider adding user_id for traceability.

-        logger.error("[delete_user_me] Attempt to delete superuser account by itself")
+        logger.error(
+            "[delete_user_me] Attempt to delete superuser account by itself | user_id=%s",
+            current_user.id,
+        )

217-217: Keep error severity; consider adding user context

Self-deletion attempt by superuser is misuse; error is appropriate. Consider logging the user_id.

-        logger.error("[delete_user] Attempt to delete self by superuser")
+        logger.error(
+            "[delete_user] Attempt to delete self by superuser | user_id=%s",
+            current_user.id,
+        )
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fa02f65 and 9d085aa.

📒 Files selected for processing (4)
  • backend/app/api/routes/credentials.py (10 hunks)
  • backend/app/api/routes/users.py (11 hunks)
  • backend/app/crud/user.py (3 hunks)
  • backend/app/utils.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/app/utils.py
  • backend/app/api/routes/credentials.py
  • backend/app/crud/user.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/app/api/routes/users.py (4)
backend/app/crud/collection.py (1)
  • delete (107-115)
backend/app/crud/document.py (1)
  • delete (124-133)
backend/app/tests/api/routes/test_users.py (3)
  • test_update_password_me (173-209)
  • test_update_user (306-329)
  • test_update_user_me (150-170)
backend/app/api/routes/project_user.py (1)
  • add_user (23-53)
🪛 Ruff (0.12.2)
backend/app/api/routes/users.py

54-54: f-string without any placeholders

Remove extraneous f prefix

(F541)


82-82: f-string without any placeholders

Remove extraneous f prefix

(F541)


143-143: f-string without any placeholders

Remove extraneous f prefix

(F541)


194-194: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: checks (3.11.7, 6)
🔇 Additional comments (2)
backend/app/api/routes/users.py (2)

1-1: LGTM: proper logging import added

Importing logging at module level is appropriate for route-level logging.


29-29: LGTM: module-level logger initialization

logger = logging.getLogger(name) is the right pattern for namespaced logs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/app/crud/credentials.py (2)

72-85: Bug: get_key_by_org inspects encrypted string; decrypt before accessing api_key

creds.credential is stored encrypted. Checking "api_key" in creds.credential will always fail and returns None. Decrypt first, handle errors, and return the key.

 def get_key_by_org(
@@
 ) -> Optional[str]:
@@
-    creds = session.exec(statement).first()
-
-    if creds and creds.credential and "api_key" in creds.credential:
-        return creds.credential["api_key"]
-
-    return None
+    creds = session.exec(statement).first()
+    if not creds or not creds.credential:
+        return None
+    try:
+        decrypted = decrypt_credentials(creds.credential)
+    except ValueError as e:
+        logger.error(
+            "[get_key_by_org] Failed to decrypt credentials",
+            extra={"organization_id": org_id, "provider": provider, "project_id": project_id},
+            exc_info=True,
+        )
+        return None
+    # Return the API key if present
+    return decrypted.get("api_key")

191-199: Guard against missing provider credentials before soft delete

If creds is None, subsequent attribute access will raise. Handle 404 here to make the function safer for reuse beyond the route layer.

-    creds = session.exec(statement).first()
-
-    # Soft delete
-    creds.is_active = False
+    creds = session.exec(statement).first()
+    if creds is None:
+        logger.error(
+            "[remove_provider_credential] Credentials not found",
+            extra={"organization_id": org_id, "provider": provider, "project_id": project_id},
+        )
+        raise HTTPException(status_code=404, detail="Credentials not found for this provider")
+
+    # Soft delete
+    creds.is_active = False
♻️ Duplicate comments (5)
backend/app/core/providers.py (1)

2-2: Adopt built‑in generics and drop unused Optional import (Ruff UP035, F401)

This file still uses typing.List/Dict and imports Optional which is unused. Switch to built-ins and remove the unused import. This mirrors prior review guidance.

-from typing import Dict, List, Optional
+from typing import Optional  # If not used elsewhere, remove this line

@@
-    required_fields: List[str]
+    required_fields: list[str]
@@
-PROVIDER_CONFIGS: Dict[Provider, ProviderConfig] = {
+PROVIDER_CONFIGS: dict[Provider, ProviderConfig] = {
@@
-def validate_provider_credentials(provider: str, credentials: Dict[str, str]) -> None:
+def validate_provider_credentials(provider: str, credentials: dict[str, str]) -> None:
@@
-def get_supported_providers() -> List[str]:
+def get_supported_providers() -> list[str]:

If Optional isn't referenced anywhere, delete its import line entirely.

Also applies to: 21-21, 25-25, 60-60, 84-84

backend/app/api/routes/credentials.py (3)

59-61: Nice: duplicate credentials logged as warning (keep 400 for tests)

This matches earlier feedback and keeps test expectations intact.


153-165: Dead branch: validate_provider never returns falsy; convert to try/except and pass provider string to CRUD

The “if not provider_enum” path is unreachable. Catch ValueError and convert to HTTP 400. Also pass .value to CRUD for clarity.

-    provider_enum = validate_provider(provider)
-    if not provider_enum:
-        raise HTTPException(status_code=400, detail="Invalid provider")
+    try:
+        provider_enum = validate_provider(provider)
+    except ValueError as e:
+        logger.error(
+            "[delete_provider_credential] Invalid provider",
+            extra={"organization_id": org_id, "project_id": project_id, "provider": provider},
+        )
+        raise HTTPException(status_code=400, detail=str(e))
@@
-        provider=provider_enum,
+        provider=provider_enum.value,

73-77: Bug: raising built-in Exception drops status_code/detail; use HTTPException

Clients will not receive a proper HTTP response body/status with Exception(...). Replace with FastAPI/Starlette HTTPException and keep structured logging.

-        logger.error(
-            f"[create_new_credential] Failed to create credentials | organization_id: {creds_in.organization_id}, project_id: {creds_in.project_id}"
-        )
-        raise Exception(status_code=500, detail="Failed to create credentials")
+        logger.error(
+            "[create_new_credential] Failed to create credentials",
+            extra={"organization_id": creds_in.organization_id, "project_id": creds_in.project_id},
+        )
+        raise HTTPException(status_code=500, detail="Failed to create credentials")
backend/app/crud/credentials.py (1)

200-202: Consistent logging style: use extra fields for success logs

Applies to update/remove/all-remove success logs to align with structured logging approach recommended above.

Example for update:

-    logger.info(
-        f"[update_creds_for_org] Successfully updated credentials | organization_id {org_id}, provider {creds_in.provider}, project_id {creds_in.project_id}"
-    )
+    logger.info(
+        "[update_creds_for_org] Successfully updated credentials",
+        extra={"organization_id": org_id, "provider": creds_in.provider, "project_id": creds_in.project_id},
+    )

Also applies to: 223-225, 174-176, 59-61

🧹 Nitpick comments (17)
backend/app/core/providers.py (2)

52-54: Prefer structured logging with extra={} for unsupported provider

The f-string embeds contextual fields into the message, which is harder to parse downstream. Use a stable message and attach fields via extra.

-        logger.error(
-            f"[validate_provider] Unsupported provider | provider: {provider}, supported_providers: {supported}"
-        )
+        logger.error(
+            "[validate_provider] Unsupported provider",
+            extra={"provider": provider, "supported_providers": supported},
+        )

76-78: Likewise, attach missing_fields via extra rather than interpolating

Structured fields are easier to search/aggregate and avoid message churn.

-        logger.error(
-            f"[validate_provider_credentials] Missing required fields | provider: {provider}, missing_fields: {', '.join(missing_fields)}"
-        )
+        logger.error(
+            "[validate_provider_credentials] Missing required fields",
+            extra={"provider": provider, "missing_fields": missing_fields},
+        )
backend/app/api/routes/credentials.py (9)

42-44: Downgrade to warning for client-side validation errors

“Project does not belong to organization” is a 400-level client error; logging it at error inflates error rates. Use warning and attach fields via extra.

-            logger.error(
-                f"[create_new_credential] Project does not belong to organization | organization_id: {creds_in.organization_id}, project_id: {creds_in.project_id}"
-            )
+            logger.warning(
+                "[create_new_credential] Project does not belong to organization",
+                extra={"organization_id": creds_in.organization_id, "project_id": creds_in.project_id},
+            )

91-93: Not-found is expected; lower log level and add structured fields

404 on read is common; prefer info or warning and attach fields via extra. Avoid error unless it indicates an internal failure.

-        logger.error(
-            f"[read_credential] No credentials found | organization_id: {org_id}, project_id: {project_id}"
-        )
+        logger.info(
+            "[read_credential] Credentials not found",
+            extra={"organization_id": org_id, "project_id": project_id},
+        )

109-115: Be explicit: pass provider string value to CRUD and add structured logging consistently

validate_provider returns a str Enum; pass .value to be explicit to SQLModel/typing.

-    provider_creds = get_provider_credential(
+    provider_creds = get_provider_credential(
         session=session,
         org_id=org_id,
-        provider=provider_enum,
+        provider=provider_enum.value,
         project_id=project_id,
     )

117-119: 404 path: lower severity and attach fields via extra

Same reasoning as the org-level 404.

-        logger.error(
-            f"[read_provider_credential] No credentials found | organization_id: {org_id}, provider: {provider}"
-        )
+        logger.info(
+            "[read_provider_credential] Provider credentials not found",
+            extra={"organization_id": org_id, "provider": provider, "project_id": project_id},
+        )

135-135: Input validation: add structured context and avoid interpolated message

Attach org_id/provider presence to aid debugging without logging secrets.

-        logger.error(f"[update_credential] Invalid input | organization_id: {org_id}")
+        logger.error(
+            "[update_credential] Invalid input",
+            extra={
+                "organization_id": org_id,
+                "provider_provided": bool(getattr(creds_in, "provider", None)),
+                "credential_provided": bool(getattr(creds_in, "credential", None)),
+            },
+        )

166-168: 404 path: tone down severity and add fields via extra

Align log level with expected-not-found semantics.

-        logger.error(
-            f"[delete_provider_credential] Provider credentials not found | organization_id: {org_id}, provider: {provider}, project_id: {project_id}"
-        )
+        logger.info(
+            "[delete_provider_credential] Provider credentials not found",
+            extra={"organization_id": org_id, "provider": provider, "project_id": project_id},
+        )

191-197: Delete-all 404: log as info/warning with structured fields

Same rationale as other not-found reads.

-        logger.error(
-            f"[delete_all_credentials] Credentials not found | organization_id: {org_id}, project_id: {project_id}"
-        )
+        logger.info(
+            "[delete_all_credentials] Credentials not found",
+            extra={"organization_id": org_id, "project_id": project_id},
+        )

2-2: Use built-in generics in response_model and drop typing.List import

Modern Python + Pydantic work with list[...] in type params; removes the need for typing.List.

-from typing import List
+from typing import Any  # if needed elsewhere; otherwise remove

@@
-    response_model=APIResponse[List[CredsPublic]],
+    response_model=APIResponse[list[CredsPublic]],
@@
-    response_model=APIResponse[List[CredsPublic]],
+    response_model=APIResponse[list[CredsPublic]],
@@
-    response_model=APIResponse[List[CredsPublic]],
+    response_model=APIResponse[list[CredsPublic]],

If List is otherwise unused, remove the import line entirely.

Also applies to: 31-31, 85-85, 128-128


51-57: Normalize provider keys when checking duplicates

Input may use mixed case ("OpenAI"); normalize to lowercase for the duplicate check to match stored provider values.

-    for provider in creds_in.credential.keys():
+    for provider in creds_in.credential.keys():
+        provider = provider.lower()
         existing_cred = get_provider_credential(
             session=session,
             org_id=creds_in.organization_id,
             provider=provider,
             project_id=creds_in.project_id,
         )
backend/app/crud/credentials.py (6)

23-26: Client input error: consider warning + structured fields

“No credentials provided” is a 400; logging at warning with extra keeps error rates clean while preserving context.

-        logger.error(
-            f"[set_creds_for_org] No credentials provided | project_id: {creds_add.project_id}"
-        )
+        logger.warning(
+            "[set_creds_for_org] No credentials provided",
+            extra={"project_id": creds_add.project_id},
+        )

51-55: Structure IntegrityError logs; include provider/org/project in extra

Attaching fields via extra improves observability and avoids message formatting churn. exc_info=True is good—keep it.

-            logger.error(
-                f"[set_creds_for_org] Integrity error while adding credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}, provider {provider}: {str(e)}",
-                exc_info=True,
-            )
+            logger.error(
+                "[set_creds_for_org] Integrity error while adding credentials",
+                extra={
+                    "organization_id": creds_add.organization_id,
+                    "project_id": creds_add.project_id,
+                    "provider": provider,
+                },
+                exc_info=True,
+            )

59-61: Success log: prefer structured fields

For consistency with other suggestions.

-    logger.info(
-        f"[set_creds_for_org] Successfully created credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}"
-    )
+    logger.info(
+        "[set_creds_for_org] Successfully created credentials",
+        extra={"organization_id": creds_add.organization_id, "project_id": creds_add.project_id},
+    )

91-95: Query construction: avoid where(True); build conditions list instead

Passing True into where() is odd and may confuse readers/linters. Compose conditions and splat them.

Example (apply similarly to all queries in this file):

conditions = [
    Credential.organization_id == org_id,
    Credential.is_active.is_(True),
]
if project_id is not None:
    conditions.append(Credential.project_id == project_id)
statement = select(Credential).where(*conditions)

Also applies to: 118-123, 152-159, 186-190, 210-214


2-2: Adopt built-in generics (dict/list) and trim typing imports (Ruff UP035/UP006)

Replace Dict/List with dict/list; keep Optional/Any/Union as needed. Also update return annotations accordingly.

-from typing import Optional, Dict, Any, List, Union
+from typing import Optional, Any, Union
@@
-def set_creds_for_org(*, session: Session, creds_add: CredsCreate) -> List[Credential]:
+def set_creds_for_org(*, session: Session, creds_add: CredsCreate) -> list[Credential]:
@@
-) -> Optional[Union[Dict[str, Any], Credential]]:
+) -> Optional[Union[dict[str, Any], Credential]]:
@@
-def get_providers(
-    *, session: Session, org_id: int, project_id: Optional[int] = None
-) -> List[str]:
+def get_providers(
+    *, session: Session, org_id: int, project_id: Optional[int] = None
+) -> list[str]:
@@
-def update_creds_for_org(
-    *, session: Session, org_id: int, creds_in: CredsUpdate
-) -> List[Credential]:
+def update_creds_for_org(
+    *, session: Session, org_id: int, creds_in: CredsUpdate
+) -> list[Credential]:
@@
-def remove_creds_for_org(
-    *, session: Session, org_id: int, project_id: Optional[int] = None
-) -> List[Credential]:
+def remove_creds_for_org(
+    *, session: Session, org_id: int, project_id: Optional[int] = None
+) -> list[Credential]:

Also applies to: 18-18, 71-71, 89-90, 107-108, 131-134, 139-144


28-49: Atomicity/perf: commit once per batch using a transaction

set_creds_for_org commits inside the loop; a failure mid-loop can leave partial provider rows. Enclose the loop in a single transaction and commit once.

-    for provider, credentials in creds_add.credential.items():
-        ...
-        try:
-            session.add(credential)
-            session.commit()
-            session.refresh(credential)
-            created_credentials.append(credential)
-        except IntegrityError as e:
-            session.rollback()
-            ...
+    try:
+        with session as s:
+            with s.begin():  # single transaction
+                for provider, credentials in creds_add.credential.items():
+                    validate_provider(provider)
+                    validate_provider_credentials(provider, credentials)
+                    encrypted_credentials = encrypt_credentials(credentials)
+                    credential = Credential(
+                        organization_id=creds_add.organization_id,
+                        project_id=creds_add.project_id,
+                        is_active=creds_add.is_active,
+                        provider=provider.lower(),
+                        credential=encrypted_credentials,
+                    )
+                    credential.inserted_at = now()
+                    s.add(credential)
+                    created_credentials.append(credential)
+        # refresh outside loop
+        for c in created_credentials:
+            session.refresh(c)
+    except IntegrityError as e:
+        logger.error("[set_creds_for_org] Integrity error while adding credentials", exc_info=True,
+                     extra={"organization_id": creds_add.organization_id, "project_id": creds_add.project_id})
+        raise ValueError(f"Error while adding credentials: {e}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d085aa and 2636478.

📒 Files selected for processing (5)
  • backend/app/api/routes/credentials.py (10 hunks)
  • backend/app/api/routes/users.py (11 hunks)
  • backend/app/core/providers.py (3 hunks)
  • backend/app/crud/credentials.py (6 hunks)
  • backend/app/crud/user.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/app/crud/user.py
  • backend/app/api/routes/users.py
🧰 Additional context used
🧬 Code graph analysis (3)
backend/app/api/routes/credentials.py (1)
backend/app/tests/api/routes/test_creds.py (3)
  • test_update_credentials (183-217)
  • test_set_credential (32-62)
  • test_multiple_provider_credentials (390-448)
backend/app/crud/credentials.py (3)
backend/app/models/credentials.py (3)
  • Credential (50-94)
  • CredsCreate (19-28)
  • CredsUpdate (31-47)
backend/app/core/providers.py (2)
  • validate_provider (36-57)
  • validate_provider_credentials (60-81)
backend/app/core/security.py (2)
  • encrypt_credentials (144-161)
  • decrypt_credentials (164-181)
backend/app/core/providers.py (2)
backend/app/tests/core/test_providers.py (2)
  • test_validate_provider_credentials_missing_fields (17-31)
  • test_validate_provider_invalid (9-14)
backend/app/tests/crud/test_credentials.py (1)
  • test_invalid_provider (187-200)
🪛 Ruff (0.12.2)
backend/app/crud/credentials.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)


18-18: Use list instead of List for type annotation

Replace with list

(UP006)

backend/app/core/providers.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)


2-2: typing.Optional imported but unused

Remove unused import: typing.Optional

(F401)

🔇 Additional comments (4)
backend/app/core/providers.py (1)

1-1: Good: module-level logger initialization

Using a module-scoped logger via logging.getLogger(name) is correct and consistent with the rest of the PR.

Also applies to: 6-7

backend/app/api/routes/credentials.py (1)

23-23: Good: added module-level logger

Consistent with the project-wide logging changes.

backend/app/crud/credentials.py (2)

15-15: Good: module-level logger added

Consistent with logging across the PR.


162-167: Good: consistent 404 with context logged earlier

Matches API expectations and communicates failure clearly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/app/crud/credentials.py (2)

47-51: Make insert atomic; avoid per-item commits in loop.

Commit-on-each-iteration risks partial writes and hurts performance. Batch inside a single transaction and refresh once.

# Outline: refactor set_creds_for_org() body
created_credentials: list[Credential] = []
try:
    with session.begin():  # atomic
        for provider, credentials in creds_add.credential.items():
            validate_provider(provider)
            validate_provider_credentials(provider, credentials)
            enc = encrypt_credentials(credentials)
            cred = Credential(
                organization_id=organization_id,
                project_id=project_id,
                is_active=creds_add.is_active,
                provider=provider,
                credential=enc,
            )
            cred.inserted_at = now()
            session.add(cred)
            created_credentials.append(cred)
    for cred in created_credentials:
        session.refresh(cred)
except IntegrityError:
    logger.exception(
        f"[set_creds_for_org] Integrity error | organization_id: {organization_id}, project_id: {project_id}"
    )
    raise HTTPException(status_code=409, detail="Duplicate or constraint violation")

75-85: Decryption bug: attempting to index an encrypted string.

Credential.credential is encrypted; current code checks substring and indexes a str. Decrypt first.

@@
-    if creds and creds.credential and "api_key" in creds.credential:
-        return creds.credential["api_key"]
-
-    return None
+    if not creds or not creds.credential:
+        return None
+    data = decrypt_credentials(creds.credential)
+    return data.get("api_key")

Optional: validate provider up-front.

@@
-    """Fetches the API key from the credentials for the given organization and provider."""
+    """Fetches the API key from the credentials for the given organization and provider."""
+    validate_provider(provider)
♻️ Duplicate comments (2)
backend/app/crud/credentials.py (2)

25-27: Unify log format (labels, separators) across the file.

Use a single style: [fn] ... | organization_id: X, project_id: Y, provider: Z. Current lines mix label value and label: value.

Also applies to: 55-57, 61-63, 167-168, 178-180, 205-206, 228-229


25-27: Fix AttributeError and normalize log context fields (use function params, not model attrs).

CredsCreate has no organization_id/project_id fields; f-strings here will raise at runtime. Also make labels consistent (organization_id: etc.).

Apply:

@@
-        logger.error(
-            f"[set_creds_for_org] No credentials provided | project_id: {creds_add.project_id}"
-        )
+        logger.error(
+            f"[set_creds_for_org] No credentials provided | organization_id: {organization_id}, project_id: {project_id}"
+        )
@@
-            logger.error(
-                f"[set_creds_for_org] Integrity error while adding credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}, provider {provider}: {str(e)}",
-                exc_info=True,
-            )
-            raise ValueError(
-                f"Error while adding credentials for provider {provider}: {str(e)}"
-            )
+            logger.exception(
+                f"[set_creds_for_org] Integrity error while adding credentials | organization_id: {organization_id}, project_id: {project_id}, provider: {provider}"
+            )
+            raise HTTPException(
+                status_code=409,
+                detail=f"Error while adding credentials for provider {provider}",
+            )
@@
-    logger.info(
-        f"[set_creds_for_org] Successfully created credentials | organization_id {creds_add.organization_id}, project_id {creds_add.project_id}"
-    )
+    logger.info(
+        f"[set_creds_for_org] Successfully created credentials | organization_id: {organization_id}, project_id: {project_id}"
+    )

Also applies to: 55-57, 61-63

🧹 Nitpick comments (2)
backend/app/crud/credentials.py (2)

228-229: Minor: include removed count and normalize labels.

-    logger.info(
-        f"[remove_creds_for_org] Successfully removed all the credentials | organization_id {org_id}, project_id {project_id}"
-    )
+    logger.info(
+        f"[remove_creds_for_org] Successfully removed credentials | organization_id: {org_id}, project_id: {project_id}, removed_count: {len(creds)}"
+    )

2-2: Modernize typing to builtin generics (ruff UP035).

Swap typing.List/typing.Dict for list/dict and simplify unions.

-from typing import Optional, Dict, Any, List, Union
+from typing import Optional, Any, Union
@@
-) -> List[Credential]:
+) -> list[Credential]:
@@
-) -> Optional[str]:
+) -> str | None:
@@
-) -> Optional[Union[Dict[str, Any], Credential]]:
+) -> dict[str, Any] | Credential | None:
@@
-) -> List[str]:
+) -> list[str]:
@@
-) -> List[Credential]:
+) -> list[Credential]:
@@
-) -> List[Credential]:
+) -> list[Credential]:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ee6a317 and 2e1b79d.

📒 Files selected for processing (2)
  • backend/app/api/routes/credentials.py (8 hunks)
  • backend/app/crud/credentials.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/api/routes/credentials.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/crud/credentials.py (3)
backend/app/models/credentials.py (4)
  • Credential (48-92)
  • CredsCreate (19-29)
  • CredsUpdate (32-45)
  • CredsBase (9-16)
backend/app/core/providers.py (2)
  • validate_provider (36-57)
  • validate_provider_credentials (60-81)
backend/app/core/security.py (2)
  • encrypt_credentials (144-161)
  • decrypt_credentials (164-181)
🪛 Ruff (0.12.2)
backend/app/crud/credentials.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)

🔇 Additional comments (1)
backend/app/crud/credentials.py (1)

1-1: Module logger setup: LGTM.

Also applies to: 15-15

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
backend/app/crud/credentials.py (3)

47-53: Wrap per-provider inserts in a single transaction to avoid partial writes.
Current per-row commit can leave org in a half-created state if later providers fail.

Apply:

-        try:
-            session.add(credential)
-            session.commit()
-            session.refresh(credential)
-            created_credentials.append(credential)
-        except IntegrityError as e:
+        try:
+            with session.begin():
+                session.add(credential)
+                session.flush()  # ensure PKs populated
+                session.refresh(credential)
+            created_credentials.append(credential)
+        except IntegrityError as e:
             session.rollback()

83-86: Bug: attempting to read "api_key" from encrypted blob. Decrypt first.
As stored, Credential.credential is an encrypted string; current check will always fail.

Apply:

-    if creds and creds.credential and "api_key" in creds.credential:
-        return creds.credential["api_key"]
+    if creds and creds.credential:
+        try:
+            data = decrypt_credentials(creds.credential)
+        except ValueError as e:
+            logger.error(
+                f"[get_key_by_org] Failed to decrypt credentials | organization_id: {org_id}, provider: {provider}, project_id: {project_id}",
+                exc_info=True,
+            )
+            return None
+        return data.get("api_key")

145-147: Require project_id in update_creds_for_org
Calls to update_creds_for_org only appear in the test suite (1) and no production code invokes it; without project_id, selecting by organization_id + provider is ambiguous because there’s no uniqueness constraint on those columns. Change the signature to project_id: int and remove the if project_id is not None guard in the WHERE clause—update the test accordingly.

♻️ Duplicate comments (2)
backend/app/crud/credentials.py (2)

25-27: Include organization_id and use consistent key: value style in the error log.
Earlier feedback asked for uniform formatting; this spot still diverges.

Apply:

-        logger.error(
-            f"[set_creds_for_org] No credentials provided | project_id: {project_id}"
-        )
+        logger.error(
+            f"[set_creds_for_org] No credentials provided | organization_id: {organization_id}, project_id: {project_id}"
+        )

195-201: Guard missing provider before soft delete; return 404 instead of NPE.
Prior feedback still applies; accessing attrs on None will crash.

Apply:

     creds = session.exec(statement).first()

-    # Soft delete
-    creds.is_active = False
-    creds.updated_at = now()
+    if creds is None:
+        logger.error(
+            f"[remove_provider_credential] Credentials not found | organization_id: {org_id}, provider: {provider}, project_id: {project_id}"
+        )
+        raise HTTPException(status_code=404, detail="Credentials not found for this provider")
+
+    # Soft delete
+    creds.is_active = False
+    creds.updated_at = now()
@@
-    logger.info(
-        f"[remove_provider_credential] Successfully removed credentials for provider | organization_id {org_id}, provider {provider}, project_id {project_id}"
-    )
+    logger.info(
+        f"[remove_provider_credential] Successfully removed credentials for provider | organization_id: {org_id}, provider: {provider}, project_id: {project_id}"
+    )

Also applies to: 204-206

🧹 Nitpick comments (7)
backend/app/crud/credentials.py (7)

55-57: Standardize log formatting; avoid mixing key styles.
Also no need to wrap e with str(); exc_info will capture traceback.

Apply:

-            logger.error(
-                f"[set_creds_for_org] Integrity error while adding credentials | organization_id {organization_id}, project_id {project_id}, provider {provider}: {str(e)}",
-                exc_info=True,
-            )
+            logger.error(
+                f"[set_creds_for_org] Integrity error while adding credentials | organization_id: {organization_id}, project_id: {project_id}, provider: {provider}",
+                exc_info=True,
+            )

Optional: consider surfacing this as HTTP 409 Conflict (or a domain error) for uniqueness violations to distinguish client/data issues from generic ValueError.


61-63: Use consistent key: value formatting in success log.

-    logger.info(
-        f"[set_creds_for_org] Successfully created credentials | organization_id {organization_id}, project_id {project_id}"
-    )
+    logger.info(
+        f"[set_creds_for_org] Successfully created credentials | organization_id: {organization_id}, project_id: {project_id}"
+    )

32-34: Normalize provider casing at write-time to prevent case-mismatch lookups.
Store canonical enum value; you already validate.

-        validate_provider(provider)
-        validate_provider_credentials(provider, credentials)
+        provider_enum = validate_provider(provider)
+        validate_provider_credentials(provider_enum.value, credentials)
...
-            provider=provider,
+            provider=provider_enum.value,

Note: If existing rows contain mixed casing, consider a small data migration.

Also applies to: 39-45


165-168: Good fix replacing creds_in.project_id; keep log format consistent.
Switch “organization …” to “organization_id: …”.

-        logger.error(
-            f"[update_creds_for_org] Credentials not found | organization {org_id}, provider {creds_in.provider}, project_id {project_id}"
-        )
+        logger.error(
+            f"[update_creds_for_org] Credentials not found | organization_id: {org_id}, provider: {creds_in.provider}, project_id: {project_id}"
+        )

178-180: Consistent key: value formatting in success log.

-    logger.info(
-        f"[update_creds_for_org] Successfully updated credentials | organization_id {org_id}, provider {creds_in.provider}, project_id {project_id}"
-    )
+    logger.info(
+        f"[update_creds_for_org] Successfully updated credentials | organization_id: {org_id}, provider: {creds_in.provider}, project_id: {project_id}"
+    )

227-229: Consistent key: value formatting in bulk-remove success log.

-    logger.info(
-        f"[remove_creds_for_org] Successfully removed all the credentials | organization_id {org_id}, project_id {project_id}"
-    )
+    logger.info(
+        f"[remove_creds_for_org] Successfully removed all the credentials | organization_id: {org_id}, project_id: {project_id}"
+    )

2-2: Use builtin generics (PEP 585) instead of typing.List/Dict.
Minor modernization; Ruff UP035 flagged this.

Example:

  • from typing import Optional, Any, Union
  • Replace List[Credential] -> list[Credential], Dict[str, Any] -> dict[str, Any]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1b79d and 00be723.

📒 Files selected for processing (2)
  • backend/app/api/routes/credentials.py (8 hunks)
  • backend/app/crud/credentials.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/api/routes/credentials.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/crud/credentials.py (4)
backend/app/models/credentials.py (4)
  • Credential (48-92)
  • CredsCreate (19-29)
  • CredsUpdate (32-45)
  • CredsBase (9-16)
backend/app/core/providers.py (2)
  • validate_provider (36-57)
  • validate_provider_credentials (60-81)
backend/app/core/security.py (2)
  • encrypt_credentials (144-161)
  • decrypt_credentials (164-181)
backend/app/tests/crud/test_credentials.py (3)
  • test_update_creds_for_org (98-124)
  • test_set_credentials_for_org (21-48)
  • test_remove_creds_for_org (154-184)
🪛 Ruff (0.12.2)
backend/app/crud/credentials.py

2-2: typing.Dict is deprecated, use dict instead

(UP035)


2-2: typing.List is deprecated, use list instead

(UP035)

🔇 Additional comments (1)
backend/app/crud/credentials.py (1)

1-1: Module logger setup looks good.
Consistent with project-wide logging pattern.

Also applies to: 15-15

project_id=_current_user.project_id,
)
if existing_cred:
logger.warning(
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we add user_id also helps in long term to see who updated the credentials

@avirajsingh7 avirajsingh7 merged commit 21dc457 into main Sep 3, 2025
2 of 3 checks passed
@avirajsingh7 avirajsingh7 deleted the logs/creds_user_module branch September 3, 2025 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready-for-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review and Add structured logging to Credential and Utility Endpoints Review and Add structured logging to Authentication & User Management Endpoints

4 participants