From 4ea4d1db7e76d539185db1dde45adb15e25b54b5 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Wed, 16 Apr 2025 00:25:41 +0530 Subject: [PATCH 01/10] first commit:automation onboarding --- backend/app/api/main.py | 3 + backend/app/api/routes/onboarding.py | 101 +++++++++++++++++++++++++++ backend/app/crud/__init__.py | 18 +++++ 3 files changed, 122 insertions(+) create mode 100644 backend/app/api/routes/onboarding.py diff --git a/backend/app/api/main.py b/backend/app/api/main.py index d6d21fddd..0f4c71c62 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -11,6 +11,7 @@ threads, users, utils, + onboarding, ) from app.core.config import settings @@ -24,6 +25,8 @@ api_router.include_router(project.router) api_router.include_router(project_user.router) api_router.include_router(api_keys.router) +api_router.include_router(onboarding.router) + if settings.ENVIRONMENT == "local": api_router.include_router(private.router) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py new file mode 100644 index 000000000..736799e1e --- /dev/null +++ b/backend/app/api/routes/onboarding.py @@ -0,0 +1,101 @@ +import uuid + +from fastapi import APIRouter, HTTPException, Depends +from pydantic import BaseModel, EmailStr +from sqlmodel import Session + +from app.crud import create_organization, create_project, create_user, create_api_key +from app.models import ( + OrganizationCreate, + ProjectCreate, + UserCreate, + APIKeyPublic, + Organization, + Project, + User, + APIKey, +) +from app.core.security import get_password_hash +from app.api.deps import SessionDep + +router = APIRouter(tags=["onboarding"]) + + +# Pydantic models for input validation +class OnboardingRequest(BaseModel): + organization_name: str + project_name: str + email: EmailStr + password: str + user_name: str + + +class OnboardingResponse(BaseModel): + organization_id: int + project_id: int + user_id: uuid.UUID + api_key: str + + +@router.post("/onboard", response_model=OnboardingResponse) +def onboard_user(request: OnboardingRequest, session: SessionDep): + try: + # 1. Check if the organization exists or create a new one + existing_organization = ( + session.query(Organization) + .filter(Organization.name == request.organization_name) + .first() + ) + if existing_organization: + organization = existing_organization # Use the existing organization + else: + org_create = OrganizationCreate(name=request.organization_name) + organization = create_organization(session=session, org_create=org_create) + + # 2. Check if the project exists or create a new one + existing_project = ( + session.query(Project).filter(Project.name == request.project_name).first() + ) + if existing_project: + project = existing_project # Use the existing project + else: + project_create = ProjectCreate(name=request.project_name) + project = create_project(session=session, project_create=project_create) + + # 3. Check if the user already exists by email + existing_user = session.query(User).filter(User.email == request.email).first() + if existing_user: + raise HTTPException( + status_code=400, detail="User already exists with this email" + ) + + # 4. Create the user with hashed password + hashed_password = get_password_hash( + request.password + ) # Use bcrypt hash function + user_create = UserCreate( + name=request.user_name, + email=request.email, + password=request.password, # Plain password will be hashed in `create_user` + ) + user = create_user(session=session, user_create=user_create) + + # 5. Generate and store the API key using the existing function + api_key_public = create_api_key( + session=session, organization_id=organization.id, user_id=user.id + ) + + # 6. Set the user's is_superuser flag to False initially + user.is_superuser = False + session.commit() + + # Return the results to be used in the onboarding response + return { + "organization_id": organization.id, + "project_id": project.id, + "user_id": user.id, + "api_key": api_key_public.key, + } + + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/backend/app/crud/__init__.py b/backend/app/crud/__init__.py index 1edef5431..c19c098ac 100644 --- a/backend/app/crud/__init__.py +++ b/backend/app/crud/__init__.py @@ -6,3 +6,21 @@ ) from .document import DocumentCrud + +from .organization import ( + create_organization, + get_organization_by_id, + get_organization_by_name, + validate_organization, +) + +from .project import create_project, get_project_by_id, get_projects_by_organization + +from .api_key import ( + create_api_key, + get_api_key, + get_api_key_by_user_org, + get_api_key_by_value, + get_api_keys_by_organization, + delete_api_key, +) From 256d0da9e1770cc048523394a53e480536280bb0 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Sat, 19 Apr 2025 16:17:19 +0530 Subject: [PATCH 02/10] cascade delete --- .../99f4fc325617_add_organization_project_setup.py | 3 +-- backend/app/api/main.py | 2 +- backend/app/models/organization.py | 9 ++++++++- backend/app/models/project.py | 3 +++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/app/alembic/versions/99f4fc325617_add_organization_project_setup.py b/backend/app/alembic/versions/99f4fc325617_add_organization_project_setup.py index 9122c0f73..c8a7f61c9 100644 --- a/backend/app/alembic/versions/99f4fc325617_add_organization_project_setup.py +++ b/backend/app/alembic/versions/99f4fc325617_add_organization_project_setup.py @@ -37,8 +37,7 @@ def upgrade(): sa.Column("id", sa.Integer(), nullable=False), sa.Column("organization_id", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( - ["organization_id"], - ["organization.id"], + ["organization_id"], ["organization.id"], ondelete="CASCADE" ), sa.PrimaryKeyConstraint("id"), ) diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 3e280fa4d..08f44960b 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -12,7 +12,7 @@ users, utils, onboarding, - credentials + credentials, ) from app.core.config import settings diff --git a/backend/app/models/organization.py b/backend/app/models/organization.py index e646ded9b..150be01d0 100644 --- a/backend/app/models/organization.py +++ b/backend/app/models/organization.py @@ -5,6 +5,8 @@ if TYPE_CHECKING: from .credentials import Credential + from .project import Project + from .api_key import APIKey # Shared properties for an Organization @@ -29,10 +31,15 @@ class Organization(OrganizationBase, table=True): id: int = Field(default=None, primary_key=True) # Relationship back to Creds - api_keys: list["APIKey"] = Relationship(back_populates="organization") + api_keys: list["APIKey"] = Relationship( + back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + ) creds: list["Credential"] = Relationship( back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} ) + project: list["Project"] = Relationship( + back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"} + ) # Properties to return via API diff --git a/backend/app/models/project.py b/backend/app/models/project.py index 3c4dfd9aa..93ae534de 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -1,3 +1,4 @@ +from typing import Optional from sqlmodel import Field, Relationship, SQLModel @@ -29,6 +30,8 @@ class Project(ProjectBase, table=True): back_populates="project", cascade_delete=True ) + organization: Optional["Organization"] = Relationship(back_populates="project") + # Properties to return via API class ProjectPublic(ProjectBase): From 3880a437dbff5c0996a2ea08fd2b205a56832a4f Mon Sep 17 00:00:00 2001 From: nishika26 Date: Sat, 19 Apr 2025 20:33:16 +0530 Subject: [PATCH 03/10] test cases --- backend/app/api/routes/onboarding.py | 6 +- .../app/tests/api/routes/test_onboarding.py | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 backend/app/tests/api/routes/test_onboarding.py diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index 736799e1e..8d24fb2fb 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -56,12 +56,14 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): existing_project = ( session.query(Project).filter(Project.name == request.project_name).first() ) + # 2. Check if the project exists or create a new on if existing_project: project = existing_project # Use the existing project else: - project_create = ProjectCreate(name=request.project_name) + project_create = ProjectCreate( + name=request.project_name, organization_id=organization.id + ) project = create_project(session=session, project_create=project_create) - # 3. Check if the user already exists by email existing_user = session.query(User).filter(User.email == request.email).first() if existing_user: diff --git a/backend/app/tests/api/routes/test_onboarding.py b/backend/app/tests/api/routes/test_onboarding.py new file mode 100644 index 000000000..b82e696b9 --- /dev/null +++ b/backend/app/tests/api/routes/test_onboarding.py @@ -0,0 +1,132 @@ +import pytest +from fastapi.testclient import TestClient +from app.main import app # Assuming your FastAPI app is in app/main.py +from app.models import Organization, Project, User, APIKey +from app.crud import create_organization, create_project, create_user, create_api_key +from app.api.deps import SessionDep +from sqlalchemy import create_engine +from sqlmodel import Session, SQLModel +from app.core.config import settings +from app.tests.utils.utils import random_email, random_lower_string + +client = TestClient(app) + + +# Test for onboarding a new user +def test_onboard_user(client, db: Session): + # Prepare the test data + data = { + "organization_name": "TestOrg", + "project_name": "TestProject", + "email": random_email(), + "password": "testpassword123", + "user_name": "Test User", + } + + # Send the POST request to the /onboard endpoint + response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + + # Assert the response status code is 200 + assert response.status_code == 200 + + # Assert the response contains the correct data + response_data = response.json() + assert "organization_id" in response_data + assert "project_id" in response_data + assert "user_id" in response_data + assert "api_key" in response_data + + # Verify the organization, project, and user were created + organization = ( + db.query(Organization) + .filter(Organization.name == data["organization_name"]) + .first() + ) + project = db.query(Project).filter(Project.name == data["project_name"]).first() + user = db.query(User).filter(User.email == data["email"]).first() + api_key = db.query(APIKey).filter(APIKey.user_id == user.id).first() + + # Assert the organization, project, and user were created + assert organization is not None + assert project is not None + assert user is not None + assert api_key is not None + + # Assert the API key is correct + assert api_key.key == response_data["api_key"] + + # Assert that the user's is_superuser flag is False + assert user.is_superuser is False + + +# Test for the case when the user already exists +def test_create_user_existing_email(client, db: Session): + data = { + "organization_name": "TestOrg", + "project_name": "TestProject", + "email": random_email(), + "password": "testpassword123", + "user_name": "Test User", + } + + # Create a user to simulate an existing user + client.post(f"{settings.API_V1_STR}/onboard", json=data) + + # Try to create a user with the same email (should fail) + response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + + # Assert the response status code is 400 (bad request) since the user already exists + assert response.status_code == 400 + assert response.json()["detail"] == "400: User already exists with this email" + + +# Test for ensuring the is_superuser flag is false for a new user +def test_is_superuser_flag(client, db: Session): + # Prepare the test data + data = { + "organization_name": "TestOrg", + "project_name": "TestProject", + "email": random_email(), + "password": "testpassword123", + "user_name": "Test User", + } + + # Send the POST request to the /onboard endpoint + response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + + # Assert the response status code is 200 + assert response.status_code == 200 + + # Verify the user is created and the is_superuser flag is False + response_data = response.json() + user = db.query(User).filter(User.id == response_data["user_id"]).first() + assert user is not None + assert user.is_superuser is False + + +# Test for organization and project creation +def test_organization_and_project_creation(client, db: Session): + data = { + "organization_name": "NewOrg", + "project_name": "NewProject", + "email": random_email(), + "password": "newpassword123", + "user_name": "New User", + } + + # Send the POST request to the /onboard endpoint + response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + + # Assert the response status code is 200 + assert response.status_code == 200 + + # Assert that the organization and project were created + organization = ( + db.query(Organization) + .filter(Organization.name == data["organization_name"]) + .first() + ) + project = db.query(Project).filter(Project.name == data["project_name"]).first() + + assert organization is not None + assert project is not None From d3925b6f2fde2310aeca0e8bcc11755035c3268e Mon Sep 17 00:00:00 2001 From: nishika26 Date: Sat, 19 Apr 2025 21:01:12 +0530 Subject: [PATCH 04/10] hashing part removed-redundent --- backend/app/api/routes/onboarding.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index 8d24fb2fb..0e43f1773 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -40,23 +40,20 @@ class OnboardingResponse(BaseModel): @router.post("/onboard", response_model=OnboardingResponse) def onboard_user(request: OnboardingRequest, session: SessionDep): try: - # 1. Check if the organization exists or create a new one existing_organization = ( session.query(Organization) .filter(Organization.name == request.organization_name) .first() ) if existing_organization: - organization = existing_organization # Use the existing organization + organization = existing_organization else: org_create = OrganizationCreate(name=request.organization_name) organization = create_organization(session=session, org_create=org_create) - # 2. Check if the project exists or create a new one existing_project = ( session.query(Project).filter(Project.name == request.project_name).first() ) - # 2. Check if the project exists or create a new on if existing_project: project = existing_project # Use the existing project else: @@ -64,34 +61,27 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): name=request.project_name, organization_id=organization.id ) project = create_project(session=session, project_create=project_create) - # 3. Check if the user already exists by email + existing_user = session.query(User).filter(User.email == request.email).first() if existing_user: raise HTTPException( status_code=400, detail="User already exists with this email" ) - # 4. Create the user with hashed password - hashed_password = get_password_hash( - request.password - ) # Use bcrypt hash function user_create = UserCreate( name=request.user_name, email=request.email, - password=request.password, # Plain password will be hashed in `create_user` + password=request.password, ) user = create_user(session=session, user_create=user_create) - # 5. Generate and store the API key using the existing function api_key_public = create_api_key( session=session, organization_id=organization.id, user_id=user.id ) - # 6. Set the user's is_superuser flag to False initially user.is_superuser = False session.commit() - # Return the results to be used in the onboarding response return { "organization_id": organization.id, "project_id": project.id, From b01a2600a8183635c827f7b7e2dd7346c0de8db3 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Sat, 19 Apr 2025 21:11:32 +0530 Subject: [PATCH 05/10] rollback error --- backend/app/api/routes/onboarding.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index 0e43f1773..feffc3e33 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -80,6 +80,7 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): ) user.is_superuser = False + session.add(user) session.commit() return { @@ -90,4 +91,5 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): } except Exception as e: + session.rollback() raise HTTPException(status_code=400, detail=str(e)) From 31809d58ef882d4ace601963fc1a56b4ff6bf1bb Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Apr 2025 01:39:23 +0530 Subject: [PATCH 06/10] authorization --- backend/app/api/routes/onboarding.py | 12 +++++-- .../app/tests/api/routes/test_onboarding.py | 34 ++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index feffc3e33..aacce0773 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -16,7 +16,11 @@ APIKey, ) from app.core.security import get_password_hash -from app.api.deps import SessionDep +from app.api.deps import ( + CurrentUser, + SessionDep, + get_current_active_superuser, +) router = APIRouter(tags=["onboarding"]) @@ -37,7 +41,11 @@ class OnboardingResponse(BaseModel): api_key: str -@router.post("/onboard", response_model=OnboardingResponse) +@router.post( + "/onboard", + dependencies=[Depends(get_current_active_superuser)], + response_model=OnboardingResponse, +) def onboard_user(request: OnboardingRequest, session: SessionDep): try: existing_organization = ( diff --git a/backend/app/tests/api/routes/test_onboarding.py b/backend/app/tests/api/routes/test_onboarding.py index b82e696b9..a238e5cf2 100644 --- a/backend/app/tests/api/routes/test_onboarding.py +++ b/backend/app/tests/api/routes/test_onboarding.py @@ -13,7 +13,7 @@ # Test for onboarding a new user -def test_onboard_user(client, db: Session): +def test_onboard_user(client, db: Session, superuser_token_headers: dict[str, str]): # Prepare the test data data = { "organization_name": "TestOrg", @@ -24,7 +24,9 @@ def test_onboard_user(client, db: Session): } # Send the POST request to the /onboard endpoint - response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + response = client.post( + f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers + ) # Assert the response status code is 200 assert response.status_code == 200 @@ -60,7 +62,9 @@ def test_onboard_user(client, db: Session): # Test for the case when the user already exists -def test_create_user_existing_email(client, db: Session): +def test_create_user_existing_email( + client, db: Session, superuser_token_headers: dict[str, str] +): data = { "organization_name": "TestOrg", "project_name": "TestProject", @@ -70,10 +74,14 @@ def test_create_user_existing_email(client, db: Session): } # Create a user to simulate an existing user - client.post(f"{settings.API_V1_STR}/onboard", json=data) + client.post( + f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers + ) # Try to create a user with the same email (should fail) - response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + response = client.post( + f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers + ) # Assert the response status code is 400 (bad request) since the user already exists assert response.status_code == 400 @@ -81,7 +89,9 @@ def test_create_user_existing_email(client, db: Session): # Test for ensuring the is_superuser flag is false for a new user -def test_is_superuser_flag(client, db: Session): +def test_is_superuser_flag( + client, db: Session, superuser_token_headers: dict[str, str] +): # Prepare the test data data = { "organization_name": "TestOrg", @@ -92,7 +102,9 @@ def test_is_superuser_flag(client, db: Session): } # Send the POST request to the /onboard endpoint - response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + response = client.post( + f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers + ) # Assert the response status code is 200 assert response.status_code == 200 @@ -105,7 +117,9 @@ def test_is_superuser_flag(client, db: Session): # Test for organization and project creation -def test_organization_and_project_creation(client, db: Session): +def test_organization_and_project_creation( + client, db: Session, superuser_token_headers: dict[str, str] +): data = { "organization_name": "NewOrg", "project_name": "NewProject", @@ -115,7 +129,9 @@ def test_organization_and_project_creation(client, db: Session): } # Send the POST request to the /onboard endpoint - response = client.post(f"{settings.API_V1_STR}/onboard", json=data) + response = client.post( + f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers + ) # Assert the response status code is 200 assert response.status_code == 200 From a97a4e3468f4ff0e18046729d939b7a2b452e84f Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Apr 2025 01:43:48 +0530 Subject: [PATCH 07/10] function doc --- backend/app/api/routes/onboarding.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index aacce0773..966c41543 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -47,6 +47,12 @@ class OnboardingResponse(BaseModel): response_model=OnboardingResponse, ) def onboard_user(request: OnboardingRequest, session: SessionDep): + """ + Handles quick onboarding of a new user. + + Accepts Organization name, project name, email, password and user name, then gives back an API key which + will be further used for authentication. + """ try: existing_organization = ( session.query(Organization) From a316fd0aea666185938a75d7f1723daf34d5b1af Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Apr 2025 01:45:05 +0530 Subject: [PATCH 08/10] function doc --- backend/app/api/routes/onboarding.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index 966c41543..40c015368 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -48,9 +48,7 @@ class OnboardingResponse(BaseModel): ) def onboard_user(request: OnboardingRequest, session: SessionDep): """ - Handles quick onboarding of a new user. - - Accepts Organization name, project name, email, password and user name, then gives back an API key which + Handles quick onboarding of a new user : Accepts Organization name, project name, email, password and user name, then gives back an API key which will be further used for authentication. """ try: From fa5847f47c9d9ecdc0e56c610edc209cc2687f55 Mon Sep 17 00:00:00 2001 From: nishika26 Date: Thu, 24 Apr 2025 21:42:28 +0530 Subject: [PATCH 09/10] test cases update for hashed api key --- .../app/tests/api/routes/test_onboarding.py | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/backend/app/tests/api/routes/test_onboarding.py b/backend/app/tests/api/routes/test_onboarding.py index a238e5cf2..150222dea 100644 --- a/backend/app/tests/api/routes/test_onboarding.py +++ b/backend/app/tests/api/routes/test_onboarding.py @@ -8,13 +8,12 @@ from sqlmodel import Session, SQLModel from app.core.config import settings from app.tests.utils.utils import random_email, random_lower_string +from app.core.security import decrypt_api_key client = TestClient(app) -# Test for onboarding a new user def test_onboard_user(client, db: Session, superuser_token_headers: dict[str, str]): - # Prepare the test data data = { "organization_name": "TestOrg", "project_name": "TestProject", @@ -23,22 +22,18 @@ def test_onboard_user(client, db: Session, superuser_token_headers: dict[str, st "user_name": "Test User", } - # Send the POST request to the /onboard endpoint response = client.post( f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers ) - # Assert the response status code is 200 assert response.status_code == 200 - # Assert the response contains the correct data response_data = response.json() assert "organization_id" in response_data assert "project_id" in response_data assert "user_id" in response_data assert "api_key" in response_data - # Verify the organization, project, and user were created organization = ( db.query(Organization) .filter(Organization.name == data["organization_name"]) @@ -48,20 +43,20 @@ def test_onboard_user(client, db: Session, superuser_token_headers: dict[str, st user = db.query(User).filter(User.email == data["email"]).first() api_key = db.query(APIKey).filter(APIKey.user_id == user.id).first() - # Assert the organization, project, and user were created assert organization is not None assert project is not None assert user is not None assert api_key is not None - # Assert the API key is correct - assert api_key.key == response_data["api_key"] + plain_token = response_data["api_key"] + encrypted_stored = api_key.key + + assert decrypt_api_key(encrypted_stored) == plain_token # main check + assert encrypted_stored != plain_token - # Assert that the user's is_superuser flag is False assert user.is_superuser is False -# Test for the case when the user already exists def test_create_user_existing_email( client, db: Session, superuser_token_headers: dict[str, str] ): @@ -73,26 +68,21 @@ def test_create_user_existing_email( "user_name": "Test User", } - # Create a user to simulate an existing user client.post( f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers ) - # Try to create a user with the same email (should fail) response = client.post( f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers ) - # Assert the response status code is 400 (bad request) since the user already exists assert response.status_code == 400 assert response.json()["detail"] == "400: User already exists with this email" -# Test for ensuring the is_superuser flag is false for a new user def test_is_superuser_flag( client, db: Session, superuser_token_headers: dict[str, str] ): - # Prepare the test data data = { "organization_name": "TestOrg", "project_name": "TestProject", @@ -101,22 +91,18 @@ def test_is_superuser_flag( "user_name": "Test User", } - # Send the POST request to the /onboard endpoint response = client.post( f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers ) - # Assert the response status code is 200 assert response.status_code == 200 - # Verify the user is created and the is_superuser flag is False response_data = response.json() user = db.query(User).filter(User.id == response_data["user_id"]).first() assert user is not None assert user.is_superuser is False -# Test for organization and project creation def test_organization_and_project_creation( client, db: Session, superuser_token_headers: dict[str, str] ): @@ -128,15 +114,12 @@ def test_organization_and_project_creation( "user_name": "New User", } - # Send the POST request to the /onboard endpoint response = client.post( f"{settings.API_V1_STR}/onboard", json=data, headers=superuser_token_headers ) - # Assert the response status code is 200 assert response.status_code == 200 - # Assert that the organization and project were created organization = ( db.query(Organization) .filter(Organization.name == data["organization_name"]) From 88de6cceed33f09b6801b5087f3543bda1313e8b Mon Sep 17 00:00:00 2001 From: nishika26 Date: Fri, 2 May 2025 12:55:45 +0530 Subject: [PATCH 10/10] changes --- backend/app/api/routes/onboarding.py | 49 ++++++++++++------- .../app/tests/api/routes/test_onboarding.py | 5 +- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/backend/app/api/routes/onboarding.py b/backend/app/api/routes/onboarding.py index 40c015368..0adcfd73b 100644 --- a/backend/app/api/routes/onboarding.py +++ b/backend/app/api/routes/onboarding.py @@ -4,7 +4,14 @@ from pydantic import BaseModel, EmailStr from sqlmodel import Session -from app.crud import create_organization, create_project, create_user, create_api_key +from app.crud import ( + create_organization, + get_organization_by_name, + create_project, + create_user, + create_api_key, + get_api_key_by_user_org, +) from app.models import ( OrganizationCreate, ProjectCreate, @@ -52,10 +59,8 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): will be further used for authentication. """ try: - existing_organization = ( - session.query(Organization) - .filter(Organization.name == request.organization_name) - .first() + existing_organization = get_organization_by_name( + session=session, name=request.organization_name ) if existing_organization: organization = existing_organization @@ -76,16 +81,24 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): existing_user = session.query(User).filter(User.email == request.email).first() if existing_user: - raise HTTPException( - status_code=400, detail="User already exists with this email" + user = existing_user + else: + user_create = UserCreate( + name=request.user_name, + email=request.email, + password=request.password, ) + user = create_user(session=session, user_create=user_create) - user_create = UserCreate( - name=request.user_name, - email=request.email, - password=request.password, + existing_key = get_api_key_by_user_org( + db=session, organization_id=organization.id, user_id=user.id ) - user = create_user(session=session, user_create=user_create) + + if existing_key: + raise HTTPException( + status_code=400, + detail="API key already exists for this user and organization", + ) api_key_public = create_api_key( session=session, organization_id=organization.id, user_id=user.id @@ -95,12 +108,12 @@ def onboard_user(request: OnboardingRequest, session: SessionDep): session.add(user) session.commit() - return { - "organization_id": organization.id, - "project_id": project.id, - "user_id": user.id, - "api_key": api_key_public.key, - } + return OnboardingResponse( + organization_id=organization.id, + project_id=project.id, + user_id=user.id, + api_key=api_key_public.key, + ) except Exception as e: session.rollback() diff --git a/backend/app/tests/api/routes/test_onboarding.py b/backend/app/tests/api/routes/test_onboarding.py index 150222dea..f98b128c8 100644 --- a/backend/app/tests/api/routes/test_onboarding.py +++ b/backend/app/tests/api/routes/test_onboarding.py @@ -77,7 +77,10 @@ def test_create_user_existing_email( ) assert response.status_code == 400 - assert response.json()["detail"] == "400: User already exists with this email" + assert ( + response.json()["detail"] + == "400: API key already exists for this user and organization" + ) def test_is_superuser_flag(