From 9761018bd3aa32dbf4fef4dcd08f7d4fa366b3fa Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 3 Jul 2025 15:15:24 +0530 Subject: [PATCH 01/13] data models --- backend/app/models/assistants.py | 8 ++++++-- backend/app/models/collection.py | 2 +- backend/app/models/credentials.py | 8 ++++++-- backend/app/models/organization.py | 10 +++++----- backend/app/models/project.py | 12 +++++++----- backend/app/models/user.py | 2 +- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/backend/app/models/assistants.py b/backend/app/models/assistants.py index 4163297df..5a0c87162 100644 --- a/backend/app/models/assistants.py +++ b/backend/app/models/assistants.py @@ -13,8 +13,12 @@ class AssistantBase(SQLModel): vector_store_id: str temperature: float = 0.1 max_num_results: int = 20 - project_id: int = Field(foreign_key="project.id") - organization_id: int = Field(foreign_key="organization.id") + project_id: int = Field( + foreign_key="project.id", nullable=False, ondelete="CASCADE" + ) + organization_id: int = Field( + foreign_key="organization.id", nullable=False, ondelete="CASCADE" + ) class Assistant(AssistantBase, table=True): diff --git a/backend/app/models/collection.py b/backend/app/models/collection.py index 27bc66e40..965771a33 100644 --- a/backend/app/models/collection.py +++ b/backend/app/models/collection.py @@ -35,7 +35,7 @@ class Collection(SQLModel, table=True): project_id: int = Field( foreign_key="project.id", - nullable=True, + nullable=False, ondelete="CASCADE", ) diff --git a/backend/app/models/credentials.py b/backend/app/models/credentials.py index 0a05cff2d..97cfaae07 100644 --- a/backend/app/models/credentials.py +++ b/backend/app/models/credentials.py @@ -7,8 +7,12 @@ class CredsBase(SQLModel): - organization_id: int = Field(foreign_key="organization.id") - project_id: Optional[int] = Field(default=None, foreign_key="project.id") + organization_id: int = Field( + foreign_key="organization.id", nullable=False, ondelete="CASCADE" + ) + project_id: Optional[int] = Field( + default=None, foreign_key="project.id", nullable=False, ondelete="CASCADE" + ) is_active: bool = True diff --git a/backend/app/models/organization.py b/backend/app/models/organization.py index fc52bf837..90eed18be 100644 --- a/backend/app/models/organization.py +++ b/backend/app/models/organization.py @@ -38,19 +38,19 @@ class Organization(OrganizationBase, table=True): # Relationship back to Creds api_keys: list["APIKey"] = Relationship( - back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="organization", cascade_delete=True ) creds: list["Credential"] = Relationship( - back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="organization", cascade_delete=True ) project: list["Project"] = Relationship( - back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="organization", cascade_delete=True ) assistants: list["Assistant"] = Relationship( - back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="organization", cascade_delete=True ) collections: list["Collection"] = Relationship( - back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="organization", cascade_delete=True ) diff --git a/backend/app/models/project.py b/backend/app/models/project.py index df63f0d41..de2ceb3ce 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -27,7 +27,9 @@ class ProjectUpdate(SQLModel): # Database model for Project class Project(ProjectBase, table=True): id: int = Field(default=None, primary_key=True) - organization_id: int = Field(foreign_key="organization.id", index=True) + organization_id: int = Field( + foreign_key="organization.id", index=True, nullable=False, ondelete="CASCADE" + ) inserted_at: datetime = Field(default_factory=now, nullable=False) updated_at: datetime = Field(default_factory=now, nullable=False) @@ -35,17 +37,17 @@ class Project(ProjectBase, table=True): back_populates="project", cascade_delete=True ) creds: list["Credential"] = Relationship( - back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="project", cascade_delete=True ) assistants: list["Assistant"] = Relationship( - back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="project", cascade_delete=True ) api_keys: list["APIKey"] = Relationship( - back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="project", cascade_delete=True ) organization: Optional["Organization"] = Relationship(back_populates="project") collections: list["Collection"] = Relationship( - back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"} + back_populates="project", cascade_delete=True ) diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 66a7e932c..fa4e39670 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -57,7 +57,7 @@ class User(UserBase, table=True): projects: list["ProjectUser"] = Relationship( back_populates="user", cascade_delete=True ) - api_keys: list["APIKey"] = Relationship(back_populates="user") + api_keys: list["APIKey"] = Relationship(back_populates="user", cascade_delete=True) class UserOrganization(UserBase): From 70f0b018dfe5fee08eeed371ece83e1a010b3673 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 3 Jul 2025 16:12:18 +0530 Subject: [PATCH 02/13] creds table --- backend/app/models/credentials.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/models/credentials.py b/backend/app/models/credentials.py index 97cfaae07..e3585cba2 100644 --- a/backend/app/models/credentials.py +++ b/backend/app/models/credentials.py @@ -57,16 +57,16 @@ class Credential(CredsBase, table=True): index=True, description="Provider name like 'openai', 'gemini'" ) credential: str = Field( - sa_column=sa.Column(sa.String), + sa_column=sa.Column(sa.String, nullable=False), description="Encrypted provider-specific credentials", ) inserted_at: datetime = Field( default_factory=now, - sa_column=sa.Column(sa.DateTime, default=datetime.utcnow), + sa_column=sa.Column(sa.DateTime, default=datetime.utcnow, nullable=False), ) updated_at: datetime = Field( default_factory=now, - sa_column=sa.Column(sa.DateTime, onupdate=datetime.utcnow), + sa_column=sa.Column(sa.DateTime, onupdate=datetime.utcnow, nullable=False), ) deleted_at: Optional[datetime] = Field( default=None, sa_column=sa.Column(sa.DateTime, nullable=True) From a58b81303cef2e3d34a26d9a49ba851ca2807dc9 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 3 Jul 2025 16:56:36 +0530 Subject: [PATCH 03/13] inconsistency fix migration --- .../4aa1f48c6321_add_inconistency_fixes.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py new file mode 100644 index 000000000..880ac2a84 --- /dev/null +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -0,0 +1,84 @@ +"""add anything + +Revision ID: 4aa1f48c6321 +Revises: 3389c67fdcb4 +Create Date: 2025-07-03 16:46:13.642386 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "4aa1f48c6321" +down_revision = "3389c67fdcb4" +branch_labels = None +depends_on = None + + +def upgrade(): + op.alter_column( + "collection", "project_id", existing_type=sa.INTEGER(), nullable=False + ) + op.alter_column( + "credential", "project_id", existing_type=sa.INTEGER(), nullable=False + ) + op.alter_column( + "credential", + "inserted_at", + existing_type=postgresql.TIMESTAMP(), + nullable=False, + ) + op.alter_column( + "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=False + ) + op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") + op.create_foreign_key( + None, "credential", "project", ["project_id"], ["id"], ondelete="CASCADE" + ) + op.create_index( + op.f("ix_openai_assistant_assistant_id"), + "openai_assistant", + ["assistant_id"], + unique=True, + ) + op.drop_constraint("project_organization_id_fkey", "project", type_="foreignkey") + op.create_foreign_key( + None, "project", "organization", ["organization_id"], ["id"], ondelete="CASCADE" + ) + + +def downgrade(): + op.drop_constraint(None, "project", type_="foreignkey") + op.create_foreign_key( + "project_organization_id_fkey", + "project", + "organization", + ["organization_id"], + ["id"], + ) + op.drop_index( + op.f("ix_openai_assistant_assistant_id"), table_name="openai_assistant" + ) + op.drop_constraint(None, "credential", type_="foreignkey") + op.create_foreign_key( + "credential_project_id_fkey", + "credential", + "project", + ["project_id"], + ["id"], + ondelete="SET NULL", + ) + op.alter_column( + "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True + ) + op.alter_column( + "credential", "inserted_at", existing_type=postgresql.TIMESTAMP(), nullable=True + ) + op.alter_column( + "credential", "project_id", existing_type=sa.INTEGER(), nullable=True + ) + op.alter_column( + "collection", "project_id", existing_type=sa.INTEGER(), nullable=True + ) From 04737c9a7814be7751c572661aede6424e97d754 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 3 Jul 2025 20:08:33 +0530 Subject: [PATCH 04/13] remove project id as non nullable --- .../versions/4aa1f48c6321_add_inconistency_fixes.py | 7 ------- backend/app/models/credentials.py | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index 880ac2a84..acdb6f7eb 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -21,9 +21,6 @@ def upgrade(): op.alter_column( "collection", "project_id", existing_type=sa.INTEGER(), nullable=False ) - op.alter_column( - "credential", "project_id", existing_type=sa.INTEGER(), nullable=False - ) op.alter_column( "credential", "inserted_at", @@ -33,10 +30,6 @@ def upgrade(): op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=False ) - op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") - op.create_foreign_key( - None, "credential", "project", ["project_id"], ["id"], ondelete="CASCADE" - ) op.create_index( op.f("ix_openai_assistant_assistant_id"), "openai_assistant", diff --git a/backend/app/models/credentials.py b/backend/app/models/credentials.py index e3585cba2..693143553 100644 --- a/backend/app/models/credentials.py +++ b/backend/app/models/credentials.py @@ -11,7 +11,7 @@ class CredsBase(SQLModel): foreign_key="organization.id", nullable=False, ondelete="CASCADE" ) project_id: Optional[int] = Field( - default=None, foreign_key="project.id", nullable=False, ondelete="CASCADE" + default=None, foreign_key="project.id", ondelete="CASCADE" ) is_active: bool = True From 319db65e726f4fc0360887ba56dedfb7df5a845a Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 3 Jul 2025 20:10:37 +0530 Subject: [PATCH 05/13] removing unneseccary --- .../alembic/versions/4aa1f48c6321_add_inconistency_fixes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index acdb6f7eb..b54499580 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -69,9 +69,6 @@ def downgrade(): op.alter_column( "credential", "inserted_at", existing_type=postgresql.TIMESTAMP(), nullable=True ) - op.alter_column( - "credential", "project_id", existing_type=sa.INTEGER(), nullable=True - ) op.alter_column( "collection", "project_id", existing_type=sa.INTEGER(), nullable=True ) From 14dfd43da6c297d346c49fc7e71a0464ba42ec96 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Fri, 4 Jul 2025 15:00:29 +0530 Subject: [PATCH 06/13] project id non nullable creds table --- .../4aa1f48c6321_add_inconistency_fixes.py | 21 +++- backend/app/models/credentials.py | 2 +- backend/app/tests/api/routes/test_creds.py | 110 +++++++++--------- backend/app/tests/crud/test_credentials.py | 96 ++++++++------- 4 files changed, 120 insertions(+), 109 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index b54499580..fa447b828 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -1,4 +1,4 @@ -"""add anything +"""add inconsistency fixes Revision ID: 4aa1f48c6321 Revises: 3389c67fdcb4 @@ -30,6 +30,9 @@ def upgrade(): op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=False ) + op.alter_column( + "credential", "project_id", existing_type=sa.INTEGER(), nullable=False + ) op.create_index( op.f("ix_openai_assistant_assistant_id"), "openai_assistant", @@ -40,6 +43,10 @@ def upgrade(): op.create_foreign_key( None, "project", "organization", ["organization_id"], ["id"], ondelete="CASCADE" ) + op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey") + op.create_foreign_key( + None, "credential", "project", ["project_id"], ["id"], ondelete="CASCADE" + ) def downgrade(): @@ -63,6 +70,15 @@ def downgrade(): ["id"], ondelete="SET NULL", ) + op.drop_constraint(None, "credential", type_="foreignkey") + op.create_foreign_key( + "credential_project_id_fkey", + "credential", + "project", + ["project_id"], + ["id"], + ondelete="SET NULL", + ) op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True ) @@ -72,3 +88,6 @@ def downgrade(): op.alter_column( "collection", "project_id", existing_type=sa.INTEGER(), nullable=True ) + op.alter_column( + "credential", "project_id", existing_type=sa.INTEGER(), nullable=True + ) diff --git a/backend/app/models/credentials.py b/backend/app/models/credentials.py index 693143553..e3585cba2 100644 --- a/backend/app/models/credentials.py +++ b/backend/app/models/credentials.py @@ -11,7 +11,7 @@ class CredsBase(SQLModel): foreign_key="organization.id", nullable=False, ondelete="CASCADE" ) project_id: Optional[int] = Field( - default=None, foreign_key="project.id", ondelete="CASCADE" + default=None, foreign_key="project.id", nullable=False, ondelete="CASCADE" ) is_active: bool = True diff --git a/backend/app/tests/api/routes/test_creds.py b/backend/app/tests/api/routes/test_creds.py index 428d4c1c7..29299e690 100644 --- a/backend/app/tests/api/routes/test_creds.py +++ b/backend/app/tests/api/routes/test_creds.py @@ -3,13 +3,12 @@ from sqlmodel import Session import random import string +import uuid from app.main import app -from app.api.deps import get_db from app.crud.credentials import set_creds_for_org -from app.models import CredsCreate, Organization, OrganizationCreate, Project +from app.models import CredsCreate, Organization, Project from app.core.config import settings -from app.core.security import encrypt_api_key from app.core.providers import Provider from app.models.credentials import Credential from app.core.security import decrypt_credentials @@ -23,16 +22,27 @@ def generate_random_string(length=10): @pytest.fixture -def create_organization_and_creds(db: Session): - unique_org_name = "Test Organization " + generate_random_string(5) - org = Organization(name=unique_org_name, is_active=True) +def create_org_and_project_creds(db: Session): + """Helper function to create an organization and a project.""" + org = Organization(name=f"Test Organization {uuid.uuid4()}", is_active=True) db.add(org) db.commit() db.refresh(org) + project = Project( + name=f"Test Project {uuid.uuid4()}", + description="A test project", + organization_id=org.id, + is_active=True, + ) + db.add(project) + db.commit() + db.refresh(project) + api_key = "sk-" + generate_random_string(10) creds_data = CredsCreate( organization_id=org.id, + project_id=project.id, is_active=True, credential={ Provider.OPENAI.value: { @@ -42,31 +52,17 @@ def create_organization_and_creds(db: Session): } }, ) - return org, creds_data + return org, project, creds_data -def test_set_creds_for_org(db: Session, superuser_token_headers: dict[str, str]): - org = Organization(name="Org for Set Creds", is_active=True) - db.add(org) - db.commit() - db.refresh(org) - - api_key = "sk-" + generate_random_string(10) - creds_data = { - "organization_id": org.id, - "is_active": True, - "credential": { - Provider.OPENAI.value: { - "api_key": api_key, - "model": "gpt-4", - "temperature": 0.7, - } - }, - } +def test_set_creds_for_org( + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds +): + org, project, creds_data = create_org_and_project_creds response = client.post( f"{settings.API_V1_STR}/credentials/", - json=creds_data, + json=creds_data.dict(), headers=superuser_token_headers, ) @@ -75,6 +71,7 @@ def test_set_creds_for_org(db: Session, superuser_token_headers: dict[str, str]) assert isinstance(data, list) assert len(data) == 1 assert data[0]["organization_id"] == org.id + assert data[0]["project_id"] == project.id assert data[0]["provider"] == Provider.OPENAI.value assert data[0]["credential"]["model"] == "gpt-4" @@ -94,11 +91,10 @@ def test_set_creds_for_invalid_project_org_relationship( db.add(project2) db.commit() - # Invalid case: Organization mismatch (org1's creds for project2 of org2) creds_data_invalid = { "organization_id": org1.id, "is_active": True, - "project_id": project2.id, # Invalid project for org1 + "project_id": project2.id, "credential": {Provider.OPENAI.value: {"api_key": "sk-123", "model": "gpt-4"}}, } @@ -116,13 +112,10 @@ def test_set_creds_for_invalid_project_org_relationship( def test_set_creds_for_project_not_found( - db: Session, superuser_token_headers: dict[str, str] + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): # Setup: Create an organization but no project - org = Organization(name="Org for Project Not Found", is_active=True) - db.add(org) - db.commit() - db.refresh(org) + org, __, _ = create_org_and_project_creds creds_data_invalid_project = { "organization_id": org.id, @@ -142,9 +135,9 @@ def test_set_creds_for_project_not_found( def test_read_credentials_with_creds( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, project, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) response = client.get( @@ -157,6 +150,7 @@ def test_read_credentials_with_creds( assert isinstance(data, list) assert len(data) == 1 assert data[0]["organization_id"] == org.id + assert data[0]["project_id"] == project.id assert data[0]["provider"] == Provider.OPENAI.value assert data[0]["credential"]["model"] == "gpt-4" @@ -173,9 +167,9 @@ def test_read_credentials_not_found( def test_read_provider_credential( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, project, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) response = client.get( @@ -190,9 +184,9 @@ def test_read_provider_credential( def test_read_provider_credential_not_found( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, _ = create_organization_and_creds + org, _, _ = create_org_and_project_creds response = client.get( f"{settings.API_V1_STR}/credentials/{org.id}/{Provider.OPENAI.value}", @@ -204,9 +198,9 @@ def test_read_provider_credential_not_found( def test_update_credentials( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) update_data = { @@ -234,9 +228,9 @@ def test_update_credentials( def test_update_credentials_failed_update( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) @@ -299,9 +293,9 @@ def test_update_credentials_not_found( def test_delete_provider_credential( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, project, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) response = client.delete( @@ -315,9 +309,9 @@ def test_delete_provider_credential( def test_delete_provider_credential_not_found( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, _ = create_organization_and_creds + org, _, _ = create_org_and_project_creds response = client.delete( f"{settings.API_V1_STR}/credentials/{org.id}/{Provider.OPENAI.value}", @@ -330,9 +324,9 @@ def test_delete_provider_credential_not_found( def test_delete_all_credentials( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds set_creds_for_org(session=db, creds_add=creds_data) response = client.delete( @@ -366,9 +360,9 @@ def test_delete_all_credentials_not_found( def test_duplicate_credential_creation( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds # First create credentials response = client.post( f"{settings.API_V1_STR}/credentials/", @@ -388,13 +382,14 @@ def test_duplicate_credential_creation( def test_multiple_provider_credentials( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, _ = create_organization_and_creds + org, project, _ = create_org_and_project_creds # Create OpenAI credentials openai_creds = { "organization_id": org.id, + "project_id": project.id, "is_active": True, "credential": { Provider.OPENAI.value: { @@ -408,6 +403,7 @@ def test_multiple_provider_credentials( # Create Langfuse credentials langfuse_creds = { "organization_id": org.id, + "project_id": project.id, "is_active": True, "credential": { Provider.LANGFUSE.value: { @@ -447,9 +443,9 @@ def test_multiple_provider_credentials( def test_credential_encryption( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds original_api_key = creds_data.credential[Provider.OPENAI.value]["api_key"] # Create credentials @@ -479,9 +475,9 @@ def test_credential_encryption( def test_credential_encryption_consistency( - db: Session, superuser_token_headers: dict[str, str], create_organization_and_creds + db: Session, superuser_token_headers: dict[str, str], create_org_and_project_creds ): - org, creds_data = create_organization_and_creds + org, _, creds_data = create_org_and_project_creds original_api_key = creds_data.credential[Provider.OPENAI.value]["api_key"] # Create credentials diff --git a/backend/app/tests/crud/test_credentials.py b/backend/app/tests/crud/test_credentials.py index f47ea8d67..9ce443167 100644 --- a/backend/app/tests/crud/test_credentials.py +++ b/backend/app/tests/crud/test_credentials.py @@ -5,8 +5,6 @@ from app.crud import credentials as credentials_crud from app.models import Credential, CredsCreate, CredsUpdate, Organization, Project -from app.tests.utils.utils import random_email -from app.core.security import get_password_hash def create_organization_and_project(db: Session) -> tuple[Organization, Project]: @@ -33,7 +31,7 @@ def create_organization_and_project(db: Session) -> tuple[Organization, Project] def test_set_creds_for_org(db: Session) -> None: """Test setting credentials for an organization.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) # Test credentials for supported providers creds_data = { @@ -45,7 +43,9 @@ def test_set_creds_for_org(db: Session) -> None: }, } - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) created_creds = credentials_crud.set_creds_for_org( session=db, creds_add=creds_create @@ -57,32 +57,11 @@ def test_set_creds_for_org(db: Session) -> None: assert {cred.provider for cred in created_creds} == {"openai", "langfuse"} -def test_set_creds_for_org_with_project(db: Session) -> None: - """Test setting credentials for an organization with a specific project.""" - organization, project = create_organization_and_project(db) - - creds_data = {"openai": {"api_key": "test-openai-key"}} - - creds_create = CredsCreate( - organization_id=organization.id, project_id=project.id, credential=creds_data - ) - - created_creds = credentials_crud.set_creds_for_org( - session=db, creds_add=creds_create - ) - - assert len(created_creds) == 1 - assert created_creds[0].organization_id == organization.id - assert created_creds[0].project_id == project.id - assert created_creds[0].provider == "openai" - assert created_creds[0].is_active - - def test_get_creds_by_org(db: Session) -> None: """Test retrieving all credentials for an organization.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) - # Set up test credentials + # Test credentials for supported providers creds_data = { "openai": {"api_key": "test-openai-key"}, "langfuse": { @@ -92,7 +71,9 @@ def test_get_creds_by_org(db: Session) -> None: }, } - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Test retrieving credentials @@ -107,17 +88,19 @@ def test_get_creds_by_org(db: Session) -> None: def test_get_provider_credential(db: Session) -> None: """Test retrieving credentials for a specific provider.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) # Set up test credentials creds_data = {"openai": {"api_key": "test-openai-key"}} - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Test retrieving specific provider credentials retrieved_cred = credentials_crud.get_provider_credential( - session=db, org_id=organization.id, provider="openai" + session=db, org_id=organization.id, project_id=project.id, provider="openai" ) assert retrieved_cred is not None @@ -127,18 +110,21 @@ def test_get_provider_credential(db: Session) -> None: def test_update_creds_for_org(db: Session) -> None: """Test updating credentials for a provider.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) + + # Set up test credentials + initial_creds = {"openai": {"api_key": "test-openai-key"}} - # Set up initial credentials - initial_creds = {"openai": {"api_key": "initial-key"}} creds_create = CredsCreate( - organization_id=organization.id, credential=initial_creds + organization_id=organization.id, project_id=project.id, credential=initial_creds ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Update credentials updated_creds = {"api_key": "updated-key"} - creds_update = CredsUpdate(provider="openai", credential=updated_creds) + creds_update = CredsUpdate( + project_id=project.id, provider="openai", credential=updated_creds + ) updated = credentials_crud.update_creds_for_org( session=db, org_id=organization.id, creds_in=creds_update @@ -154,9 +140,9 @@ def test_update_creds_for_org(db: Session) -> None: def test_remove_provider_credential(db: Session) -> None: """Test removing credentials for a specific provider.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) - # Set up test credentials + # Test credentials for supported providers creds_data = { "openai": {"api_key": "test-openai-key"}, "langfuse": { @@ -166,7 +152,9 @@ def test_remove_provider_credential(db: Session) -> None: }, } - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Remove one provider's credentials @@ -186,9 +174,9 @@ def test_remove_provider_credential(db: Session) -> None: def test_remove_creds_for_org(db: Session) -> None: """Test removing all credentials for an organization.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) - # Set up test credentials + # Test credentials for supported providers creds_data = { "openai": {"api_key": "test-openai-key"}, "langfuse": { @@ -198,7 +186,9 @@ def test_remove_creds_for_org(db: Session) -> None: }, } - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Remove all credentials @@ -217,12 +207,14 @@ def test_remove_creds_for_org(db: Session) -> None: def test_invalid_provider(db: Session) -> None: """Test handling of invalid provider names.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) # Test with unsupported provider creds_data = {"gemini": {"api_key": "test-key"}} - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) with pytest.raises(ValueError, match="Unsupported provider"): credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) @@ -230,17 +222,19 @@ def test_invalid_provider(db: Session) -> None: def test_duplicate_provider_credentials(db: Session) -> None: """Test handling of duplicate provider credentials.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) # Set up initial credentials creds_data = {"openai": {"api_key": "test-key"}} - creds_create = CredsCreate(organization_id=organization.id, credential=creds_data) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=creds_data + ) credentials_crud.set_creds_for_org(session=db, creds_add=creds_create) # Verify credentials exist and are active existing_creds = credentials_crud.get_provider_credential( - session=db, org_id=organization.id, provider="openai" + session=db, org_id=organization.id, project_id=project.id, provider="openai" ) assert existing_creds is not None assert "api_key" in existing_creds @@ -249,7 +243,7 @@ def test_duplicate_provider_credentials(db: Session) -> None: def test_langfuse_credential_validation(db: Session) -> None: """Test validation of Langfuse credentials structure.""" - organization, _ = create_organization_and_project(db) + organization, project = create_organization_and_project(db) # Test with missing required fields invalid_creds = { @@ -261,7 +255,7 @@ def test_langfuse_credential_validation(db: Session) -> None: } creds_create = CredsCreate( - organization_id=organization.id, credential=invalid_creds + organization_id=organization.id, project_id=project.id, credential=invalid_creds ) with pytest.raises(ValueError): @@ -276,7 +270,9 @@ def test_langfuse_credential_validation(db: Session) -> None: } } - creds_create = CredsCreate(organization_id=organization.id, credential=valid_creds) + creds_create = CredsCreate( + organization_id=organization.id, project_id=project.id, credential=valid_creds + ) created_creds = credentials_crud.set_creds_for_org( session=db, creds_add=creds_create From f6b5c85b6ef24d35dfa15547a8250fdcada23b7c Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 15 Jul 2025 13:22:14 +0530 Subject: [PATCH 07/13] fixing formatting --- backend/app/tests/api/routes/test_creds.py | 3 ++- backend/app/tests/crud/test_credentials.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app/tests/api/routes/test_creds.py b/backend/app/tests/api/routes/test_creds.py index 2008b587a..88aa22a28 100644 --- a/backend/app/tests/api/routes/test_creds.py +++ b/backend/app/tests/api/routes/test_creds.py @@ -1,4 +1,3 @@ -import pytest from fastapi.testclient import TestClient from sqlmodel import Session @@ -23,6 +22,7 @@ client = TestClient(app) + def create_test_credentials(db: Session): return create_test_credential(db) @@ -84,6 +84,7 @@ def test_set_credentials_for_invalid_project_org_relationship( == "Project does not belong to the specified organization" ) + def test_set_credentials_for_project_not_found( db: Session, superuser_token_headers: dict[str, str] ): diff --git a/backend/app/tests/crud/test_credentials.py b/backend/app/tests/crud/test_credentials.py index fb0ec3118..827c7e015 100644 --- a/backend/app/tests/crud/test_credentials.py +++ b/backend/app/tests/crud/test_credentials.py @@ -1,5 +1,5 @@ -from sqlmodel import Session import pytest +from sqlmodel import Session from app.crud import ( set_creds_for_org, From c9fbae9f514378f95200afedd2b86e677b992f3a Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 15 Jul 2025 13:31:28 +0530 Subject: [PATCH 08/13] test fixes --- backend/app/tests/api/routes/test_creds.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/app/tests/api/routes/test_creds.py b/backend/app/tests/api/routes/test_creds.py index 88aa22a28..52bf8429d 100644 --- a/backend/app/tests/api/routes/test_creds.py +++ b/backend/app/tests/api/routes/test_creds.py @@ -1,3 +1,4 @@ +import pytest from fastapi.testclient import TestClient from sqlmodel import Session @@ -23,6 +24,7 @@ client = TestClient(app) +@pytest.fixture def create_test_credentials(db: Session): return create_test_credential(db) @@ -388,11 +390,11 @@ def test_duplicate_credential_creation( def test_multiple_provider_credentials( db: Session, superuser_token_headers: dict[str, str] ): - org = create_test_organization(db) + project = create_test_project(db) # Create OpenAI credentials openai_credential = { - "organization_id": org.id, + "organization_id": project.organization_id, "project_id": project.id, "is_active": True, "credential": { @@ -406,7 +408,7 @@ def test_multiple_provider_credentials( # Create Langfuse credentials langfuse_credential = { - "organization_id": org.id, + "organization_id": project.organization_id, "project_id": project.id, "is_active": True, "credential": { @@ -435,7 +437,7 @@ def test_multiple_provider_credentials( # Fetch all credentials response = client.get( - f"{settings.API_V1_STR}/credentials/{org.id}", + f"{settings.API_V1_STR}/credentials/{project.organization_id}", headers=superuser_token_headers, ) assert response.status_code == 200 From c5e9a0db87eb65562d315aa325ca825709155b44 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 15 Jul 2025 23:42:17 +0530 Subject: [PATCH 09/13] small fixes --- .../versions/4aa1f48c6321_add_inconistency_fixes.py | 9 --------- backend/app/models/credentials.py | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index fa447b828..d7c59881e 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -70,15 +70,6 @@ def downgrade(): ["id"], ondelete="SET NULL", ) - op.drop_constraint(None, "credential", type_="foreignkey") - op.create_foreign_key( - "credential_project_id_fkey", - "credential", - "project", - ["project_id"], - ["id"], - ondelete="SET NULL", - ) op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True ) diff --git a/backend/app/models/credentials.py b/backend/app/models/credentials.py index e3585cba2..7096ef298 100644 --- a/backend/app/models/credentials.py +++ b/backend/app/models/credentials.py @@ -10,7 +10,7 @@ class CredsBase(SQLModel): organization_id: int = Field( foreign_key="organization.id", nullable=False, ondelete="CASCADE" ) - project_id: Optional[int] = Field( + project_id: int = Field( default=None, foreign_key="project.id", nullable=False, ondelete="CASCADE" ) is_active: bool = True From da9d0273e775d8452505384079402f54e2001a1c Mon Sep 17 00:00:00 2001 From: nishika26 Date: Wed, 16 Jul 2025 11:29:18 +0530 Subject: [PATCH 10/13] removing instruction column ambiguity --- backend/app/models/assistants.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app/models/assistants.py b/backend/app/models/assistants.py index 5a0c87162..8ba1f41ee 100644 --- a/backend/app/models/assistants.py +++ b/backend/app/models/assistants.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Optional, List from sqlmodel import Field, Relationship, SQLModel +from sqlalchemy import Text, Column from app.core.util import now @@ -8,7 +9,7 @@ class AssistantBase(SQLModel): assistant_id: str = Field(index=True, unique=True) name: str - instructions: str + instructions: str = Field(sa_column=Column(Text, nullable=False)) model: str vector_store_id: str temperature: float = 0.1 From 8357880b12c126769f856a3b144dc85e91fc8128 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Fri, 18 Jul 2025 14:17:54 +0530 Subject: [PATCH 11/13] conflicts --- .../versions/4aa1f48c6321_add_inconistency_fixes.py | 11 ++++++++++- backend/app/models/assistants.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index d7c59881e..39ab88b7f 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. revision = "4aa1f48c6321" -down_revision = "3389c67fdcb4" +down_revision = "f2589428c1d0" branch_labels = None depends_on = None @@ -70,6 +70,15 @@ def downgrade(): ["id"], ondelete="SET NULL", ) + op.drop_constraint(None, "credential", type_="foreignkey") + op.create_foreign_key( + "credential_project_id_fkey", + "credential", + "project", + ["project_id"], + ["id"], + ondelete="SET NULL", + ) op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True ) diff --git a/backend/app/models/assistants.py b/backend/app/models/assistants.py index 2ad3b1342..8546a0745 100644 --- a/backend/app/models/assistants.py +++ b/backend/app/models/assistants.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Optional, List from sqlmodel import Field, Relationship, SQLModel -from sqlalchemy import Column, String +from sqlalchemy import Column, String, Text from sqlalchemy.dialects.postgresql import ARRAY from app.core.util import now From b9a0fb7818adaafc8f98ff63cf8f05002f038371 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Tue, 15 Jul 2025 23:42:17 +0530 Subject: [PATCH 12/13] small fixes --- .../versions/4aa1f48c6321_add_inconistency_fixes.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index 39ab88b7f..5eae40ed5 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -70,15 +70,6 @@ def downgrade(): ["id"], ondelete="SET NULL", ) - op.drop_constraint(None, "credential", type_="foreignkey") - op.create_foreign_key( - "credential_project_id_fkey", - "credential", - "project", - ["project_id"], - ["id"], - ondelete="SET NULL", - ) op.alter_column( "credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True ) From 0008806f62613bdeb3cac6e7d61a4f7ad420bf3d Mon Sep 17 00:00:00 2001 From: nishika26 Date: Fri, 18 Jul 2025 14:29:52 +0530 Subject: [PATCH 13/13] fixing down revision --- .../app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py index 5eae40ed5..f8163202d 100644 --- a/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py +++ b/backend/app/alembic/versions/4aa1f48c6321_add_inconistency_fixes.py @@ -1,4 +1,4 @@ -"""add inconsistency fixes +"""Fixing inconsistencies Revision ID: 4aa1f48c6321 Revises: 3389c67fdcb4