Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f1fbe6c
moving to hard delete and fixing few is_active logic
AkhileshNegi Aug 31, 2025
4f87565
update testcases
AkhileshNegi Sep 1, 2025
4737f95
cleanups
AkhileshNegi Sep 1, 2025
f654841
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Sep 1, 2025
f6212e8
updating generated migration
AkhileshNegi Sep 1, 2025
6014fc1
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Sep 9, 2025
f3f013b
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Sep 25, 2025
c7ef7e9
added new migration to drop
AkhileshNegi Sep 25, 2025
c48bf44
reverting unnecessary changes
AkhileshNegi Sep 25, 2025
ed2380e
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Sep 25, 2025
c62943b
pre commit
AkhileshNegi Sep 25, 2025
3c2e598
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Oct 6, 2025
9f11c8a
update migration head
AkhileshNegi Oct 6, 2025
04a4810
code cleanup and optimization
AkhileshNegi Oct 6, 2025
1007e63
updating name of the migration file
AkhileshNegi Oct 6, 2025
9ced6c6
follow PEP8
AkhileshNegi Oct 6, 2025
148ab5d
following PEP8
AkhileshNegi Oct 8, 2025
1ebac46
cleanup for project id in creds
AkhileshNegi Oct 8, 2025
d62d36b
updating the message in logs
AkhileshNegi Oct 8, 2025
5b46b66
cleanup code
AkhileshNegi Oct 8, 2025
47fb0db
removed redundant code of exception handling
AkhileshNegi Oct 8, 2025
e041cae
fixes broken testcases
AkhileshNegi Oct 8, 2025
bed21be
adding unique constraint
AkhileshNegi Oct 8, 2025
4f83260
cleaning up testcases
AkhileshNegi Oct 8, 2025
1134c43
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Oct 10, 2025
e4d939a
fixed migration changes
AkhileshNegi Oct 10, 2025
ea279dc
clean migration changes
AkhileshNegi Oct 10, 2025
1ce59f8
update docs
AkhileshNegi Oct 10, 2025
1e97b17
adding test langfuse creds in seed as well
AkhileshNegi Oct 10, 2025
348f87e
using one_or_none
AkhileshNegi Oct 15, 2025
627ebc1
coderabbit suggestions
AkhileshNegi Oct 15, 2025
54f9c77
Merge branch 'main' into hotfix/credentials-updates
AkhileshNegi Oct 15, 2025
4c10661
coderabbit suggestions
AkhileshNegi Oct 15, 2025
59f3a55
updated migration head
AkhileshNegi Oct 15, 2025
f0d3148
cleanup
AkhileshNegi Oct 15, 2025
aa14727
removed raise_on_not_found
AkhileshNegi Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""drop column deleted_at from credentials

Revision ID: 27c271ab6dd0
Revises: 93d484f5798e
Create Date: 2025-10-15 11:10:02.554097

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "27c271ab6dd0"
down_revision = "93d484f5798e"
branch_labels = None
depends_on = None


def upgrade():
# Drop only deleted_at column from credential table, keep is_active for flexibility
op.drop_column("credential", "deleted_at")

# Add unique constraint on organization_id, project_id, provider
op.create_unique_constraint(
"uq_credential_org_project_provider",
"credential",
["organization_id", "project_id", "provider"],
)


def downgrade():
# Add back deleted_at column to credential table
op.add_column("credential", sa.Column("deleted_at", sa.DateTime(), nullable=True))
Comment on lines +30 to +32
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore deleted_at column definition in downgrade

The downgrade path should recreate the schema exactly as it existed before this migration. In backend/app/alembic/versions/543f97951bd0_add_credential_table.py, credential.deleted_at is declared as a timezone-aware DateTime. Reintroducing it here without timezone=True leaves the downgraded schema mismatched, which will show up in subsequent autogenerates and can break code expecting a tz-aware column. Please mirror the original definition.

-    op.add_column("credential", sa.Column("deleted_at", sa.DateTime(), nullable=True))
+    op.add_column(
+        "credential",
+        sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def downgrade():
# Add back deleted_at column to credential table
op.add_column("credential", sa.Column("deleted_at", sa.DateTime(), nullable=True))
def downgrade():
# Add back deleted_at column to credential table
op.add_column(
"credential",
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
)
🤖 Prompt for AI Agents
In backend/app/alembic/versions/6dcbc94dc165_add_new_column_credentials.py
around lines 25-27, the downgrade re-adds credential.deleted_at as sa.DateTime()
without timezone; update the op.add_column call to recreate the column with
sa.DateTime(timezone=True) so the downgraded schema exactly matches the original
migration (credential.deleted_at was timezone-aware).


# Drop the unique constraint
op.drop_constraint(
"uq_credential_org_project_provider", "credential", type_="unique"
)
60 changes: 6 additions & 54 deletions backend/app/api/routes/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
from fastapi import APIRouter, Depends

from app.api.deps import SessionDep, get_current_user_org_project
from app.core.exception_handlers import HTTPException
from app.core.providers import validate_provider
from app.crud.credentials import (
get_creds_by_org,
get_provider_credential,
remove_creds_for_org,
remove_provider_credential,
set_creds_for_org,
update_creds_for_org,
remove_provider_credential,
)
from app.models import CredsCreate, CredsPublic, CredsUpdate, UserProjectOrg
from app.utils import APIResponse
from app.core.providers import validate_provider
from app.core.exception_handlers import HTTPException

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/credentials", tags=["credentials"])
Expand All @@ -33,28 +33,8 @@ def create_new_credential(
_current_user: UserProjectOrg = Depends(get_current_user_org_project),
):
# Project comes from API key context; no cross-org check needed here
# Database unique constraint ensures no duplicate credentials per provider-org-project combination

# Prevent duplicate credentials
for provider in creds_in.credential.keys():
existing_cred = get_provider_credential(
session=session,
org_id=_current_user.organization_id,
provider=provider,
project_id=_current_user.project_id,
)
if existing_cred:
logger.warning(
f"[create_new_credential] Credentials for provider already exist | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}, provider: {provider}"
)
raise HTTPException(
status_code=400,
detail=(
f"Credentials for provider '{provider}' already exist "
f"for this organization and project combination"
),
)

# Create credentials
created_creds = set_creds_for_org(
session=session,
creds_add=creds_in,
Expand Down Expand Up @@ -86,12 +66,6 @@ def read_credential(
org_id=_current_user.organization_id,
project_id=_current_user.project_id,
)
if not creds:
logger.error(
f"[read_credential] No credentials found | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}"
)
raise HTTPException(status_code=404, detail="Credentials not found")

return APIResponse.success_response([cred.to_public() for cred in creds])


Expand All @@ -114,9 +88,6 @@ def read_provider_credential(
provider=provider_enum,
project_id=_current_user.project_id,
)
if credential is None:
raise HTTPException(status_code=404, detail="Provider credentials not found")

return APIResponse.success_response(credential)


Expand Down Expand Up @@ -165,18 +136,6 @@ def delete_provider_credential(
_current_user: UserProjectOrg = Depends(get_current_user_org_project),
):
provider_enum = validate_provider(provider)
provider_creds = get_provider_credential(
session=session,
org_id=_current_user.organization_id,
provider=provider_enum,
project_id=_current_user.project_id,
)
if provider_creds is None:
logger.error(
f"[delete_provider_credential] Provider credentials not found | organization_id: {_current_user.organization_id}, provider: {provider}, project_id: {_current_user.project_id}"
)
raise HTTPException(status_code=404, detail="Provider credentials not found")

remove_provider_credential(
session=session,
org_id=_current_user.organization_id,
Expand All @@ -193,25 +152,18 @@ def delete_provider_credential(
"/",
response_model=APIResponse[dict],
summary="Delete all credentials for current org and project",
description="Removes all credentials for the caller's organization and project. This is a soft delete operation that marks credentials as inactive.",
description="Removes all credentials for the caller's organization and project. This is a hard delete operation that permanently removes credentials from the database.",
)
def delete_all_credentials(
*,
session: SessionDep,
_current_user: UserProjectOrg = Depends(get_current_user_org_project),
):
creds = remove_creds_for_org(
remove_creds_for_org(
session=session,
org_id=_current_user.organization_id,
project_id=_current_user.project_id,
)
if not creds:
logger.error(
f"[delete_all_credentials] Credentials not found | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}"
)
raise HTTPException(
status_code=404, detail="Credentials for organization/project not found"
)

return APIResponse.success_response(
{"message": "All credentials deleted successfully"}
Expand Down
Loading