Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
50 changes: 14 additions & 36 deletions backend/app/api/routes/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
from app.crud.organization import validate_organization
from app.models import APIKeyPublic, User
from app.utils import APIResponse
from app.core.exception_handlers import HTTPException

router = APIRouter(prefix="/apikeys", tags=["API Keys"])


# Create API Key
@router.post("/", response_model=APIResponse[APIKeyPublic])
def create_key(
organization_id: int,
Expand All @@ -27,28 +27,18 @@ def create_key(
"""
Generate a new API key for the user's organization.
"""
try:
# Validate organization
validate_organization(session, organization_id)
validate_organization(session, organization_id)

existing_api_key = get_api_key_by_user_org(session, organization_id, user_id)
if existing_api_key:
raise HTTPException(
status_code=400,
detail="API Key already exists for this user and organization",
)

# Create and return API key
api_key = create_api_key(
session, organization_id=organization_id, user_id=user_id
existing_api_key = get_api_key_by_user_org(session, organization_id, user_id)
if existing_api_key:
raise HTTPException(
status_code=400,
detail="API Key already exists for this user and organization",
)
return APIResponse.success_response(api_key)

except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
api_key = create_api_key(session, organization_id=organization_id, user_id=user_id)
return APIResponse.success_response(api_key)


# List API Keys
@router.get("/", response_model=APIResponse[list[APIKeyPublic]])
def list_keys(
organization_id: int,
Expand All @@ -58,19 +48,11 @@ def list_keys(
"""
Retrieve all API keys for the user's organization.
"""
try:
# Validate organization
validate_organization(session, organization_id)

# Retrieve API keys
api_keys = get_api_keys_by_organization(session, organization_id)
return APIResponse.success_response(api_keys)

except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
validate_organization(session, organization_id)
api_keys = get_api_keys_by_organization(session, organization_id)
return APIResponse.success_response(api_keys)


# Get API Key by ID
@router.get("/{api_key_id}", response_model=APIResponse[APIKeyPublic])
def get_key(
api_key_id: int,
Expand All @@ -87,7 +69,6 @@ def get_key(
return APIResponse.success_response(api_key)


# Revoke API Key (Soft Delete)
@router.delete("/{api_key_id}", response_model=APIResponse[dict])
def revoke_key(
api_key_id: int,
Expand All @@ -97,8 +78,5 @@ def revoke_key(
"""
Soft delete an API key (revoke access).
"""
try:
delete_api_key(session, api_key_id)
return APIResponse.success_response({"message": "API key revoked successfully"})
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
delete_api_key(session, api_key_id)
return APIResponse.success_response({"message": "API key revoked successfully"})
26 changes: 4 additions & 22 deletions backend/app/api/routes/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,7 @@
collection_id: UUID = FastPath(description="Collection to retrieve"),
):
collection_crud = CollectionCrud(session, current_user.id)
try:
data = collection_crud.read_one(collection_id)
except NoResultFound as err:
raise HTTPException(status_code=404, detail=str(err))
except MultipleResultsFound as err:
raise HTTPException(status_code=503, detail=str(err))
except Exception as err:
raise_from_unknown(err)

data = collection_crud.read_one(collection_id)

Check warning on line 320 in backend/app/api/routes/collections.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/collections.py#L320

Added line #L320 was not covered by tests
return APIResponse.success_response(data)


Expand All @@ -339,13 +331,7 @@
current_user: CurrentUser,
):
collection_crud = CollectionCrud(session, current_user.id)
try:
data = collection_crud.read_all()
except (ValueError, SQLAlchemyError) as err:
raise HTTPException(status_code=403, detail=str(err))
except Exception as err:
raise_from_unknown(err)

data = collection_crud.read_all()

Check warning on line 334 in backend/app/api/routes/collections.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/collections.py#L334

Added line #L334 was not covered by tests
return APIResponse.success_response(data)


Expand All @@ -363,10 +349,6 @@
):
collection_crud = CollectionCrud(session, current_user.id)
document_collection_crud = DocumentCollectionCrud(session)
try:
collection = collection_crud.read_one(collection_id)
data = document_collection_crud.read(collection, skip, limit)
except (SQLAlchemyError, ValueError) as err:
raise HTTPException(status_code=400, detail=str(err))

collection = collection_crud.read_one(collection_id)
data = document_collection_crud.read(collection, skip, limit)

Check warning on line 353 in backend/app/api/routes/collections.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/collections.py#L352-L353

Added lines #L352 - L353 were not covered by tests
return APIResponse.success_response(data)
177 changes: 72 additions & 105 deletions backend/app/api/routes/credentials.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from fastapi import APIRouter, Depends

from app.api.deps import SessionDep, get_current_active_superuser
from app.crud.credentials import (
Expand All @@ -17,6 +16,7 @@
from app.models.project import Project
from app.utils import APIResponse
from app.core.providers import validate_provider
from app.core.exception_handlers import HTTPException

router = APIRouter(prefix="/credentials", tags=["credentials"])

Expand All @@ -29,48 +29,45 @@
description="Creates new credentials for a specific organization and project combination. This endpoint requires superuser privileges. Each organization can have different credentials for different providers and projects. Only one credential per provider is allowed per organization-project combination.",
)
def create_new_credential(*, session: SessionDep, creds_in: CredsCreate):
try:
# Check if organization exists
organization = session.get(Organization, creds_in.organization_id)
if not organization:
raise HTTPException(status_code=404, detail="Organization not found")

# Check if project exists if project_id is provided
if creds_in.project_id:
project = session.get(Project, creds_in.project_id)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
if project.organization_id != creds_in.organization_id:
raise HTTPException(
status_code=400,
detail="Project does not belong to the specified organization",
)

# Check for existing credentials for each provider
for provider in creds_in.credential.keys():
existing_cred = get_provider_credential(
session=session,
org_id=creds_in.organization_id,
provider=provider,
project_id=creds_in.project_id,
# Validate organization
organization = session.get(Organization, creds_in.organization_id)
if not organization:
raise HTTPException(status_code=404, detail="Organization not found")

Check warning on line 35 in backend/app/api/routes/credentials.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/credentials.py#L35

Added line #L35 was not covered by tests

# Validate project if provided
if creds_in.project_id:
project = session.get(Project, creds_in.project_id)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
if project.organization_id != creds_in.organization_id:
raise HTTPException(

Check warning on line 43 in backend/app/api/routes/credentials.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/credentials.py#L39-L43

Added lines #L39 - L43 were not covered by tests
status_code=400,
detail="Project does not belong to the specified organization",
)
if existing_cred:
raise HTTPException(
status_code=400,
detail=f"Credentials for provider '{provider}' already exist for this organization and project combination",
)

# Create new credentials
new_creds = set_creds_for_org(session=session, creds_add=creds_in)
if not new_creds:
raise HTTPException(status_code=500, detail="Failed to create credentials")
return APIResponse.success_response([cred.to_public() for cred in new_creds])
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"

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

# Create credentials
new_creds = set_creds_for_org(session=session, creds_add=creds_in)
if not new_creds:
raise HTTPException(status_code=500, detail="Failed to create credentials")

Check warning on line 68 in backend/app/api/routes/credentials.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/credentials.py#L68

Added line #L68 was not covered by tests

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


@router.get(
Expand All @@ -84,6 +81,7 @@
creds = get_creds_by_org(session=session, org_id=org_id, project_id=project_id)
if not creds:
raise HTTPException(status_code=404, detail="Credentials not found")

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


Expand All @@ -106,6 +104,7 @@
)
if provider_creds is None:
raise HTTPException(status_code=404, detail="Provider credentials not found")

return APIResponse.success_response(provider_creds)


Expand All @@ -117,38 +116,23 @@
description="Updates credentials for a specific organization and project combination. Can update specific provider credentials or add new providers. If project_id is provided in the update, credentials will be moved to that project. This endpoint requires superuser privileges.",
)
def update_credential(*, session: SessionDep, org_id: int, creds_in: CredsUpdate):
try:
if not creds_in or not creds_in.provider or not creds_in.credential:
raise HTTPException(
status_code=400, detail="Provider and credential must be provided"
)
organization = session.get(Organization, org_id)
if not organization:
raise HTTPException(status_code=404, detail="Organization not found")
updated_creds = update_creds_for_org(
session=session, org_id=org_id, creds_in=creds_in
)
if not updated_creds:
raise HTTPException(status_code=404, detail="Failed to update credentials")
return APIResponse.success_response(
[cred.to_public() for cred in updated_creds]
)
except IntegrityError as e:
if "ForeignKeyViolation" in str(e):
raise HTTPException(
status_code=400,
detail="Invalid organization ID. Ensure the organization exists before updating credentials.",
)
raise HTTPException(
status_code=500, detail=f"An unexpected database error occurred: {str(e)}"
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
organization = session.get(Organization, org_id)
if not organization:
raise HTTPException(status_code=404, detail="Organization not found")

if not creds_in or not creds_in.provider or not creds_in.credential:
raise HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"
status_code=400, detail="Provider and credential must be provided"
)

updated_creds = update_creds_for_org(
session=session, org_id=org_id, creds_in=creds_in
)
if not updated_creds:
raise HTTPException(status_code=404, detail="Failed to update credentials")

Check warning on line 132 in backend/app/api/routes/credentials.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/credentials.py#L132

Added line #L132 was not covered by tests

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


@router.delete(
"/{org_id}/{provider}",
Expand All @@ -160,27 +144,18 @@
def delete_provider_credential(
*, session: SessionDep, org_id: int, provider: str, project_id: int | None = None
):
try:
provider_enum = validate_provider(provider)
updated_creds = remove_provider_credential(
session=session,
org_id=org_id,
provider=provider_enum,
project_id=project_id,
)
if not updated_creds:
raise HTTPException(
status_code=404, detail="Provider credentials not found"
)
return APIResponse.success_response(
{"message": "Provider credentials removed successfully"}
)
except ValueError:
raise HTTPException(status_code=404, detail="Provider credentials not found")
except Exception as e:
raise HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"
)
provider_enum = validate_provider(provider)

updated_creds = remove_provider_credential(
session=session,
org_id=org_id,
provider=provider_enum,
project_id=project_id,
)

return APIResponse.success_response(
{"message": "Provider credentials removed successfully"}
)


@router.delete(
Expand All @@ -193,18 +168,10 @@
def delete_all_credentials(
*, session: SessionDep, org_id: int, project_id: int | None = None
):
try:
creds = remove_creds_for_org(
session=session, org_id=org_id, project_id=project_id
)
if not creds:
raise HTTPException(
status_code=404, detail="Credentials for organization not found"
)
return APIResponse.success_response(
{"message": "Credentials deleted successfully"}
)
except Exception as e:
creds = remove_creds_for_org(session=session, org_id=org_id, project_id=project_id)
if not creds:
raise HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"
status_code=404, detail="Credentials for organization not found"
)

return APIResponse.success_response({"message": "Credentials deleted successfully"})
Loading