Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 22 additions & 1 deletion backend/app/api/routes/credentials.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from fastapi import APIRouter, Depends

from app.api.deps import SessionDep, get_current_user_org_project
Expand All @@ -14,6 +16,7 @@
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 @@ -40,6 +43,9 @@ def create_new_credential(
project_id=_current_user.project_id,
)
if existing_cred:
logger.warning(
Copy link
Collaborator

Choose a reason for hiding this comment

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

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

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=(
Expand All @@ -56,7 +62,10 @@ def create_new_credential(
project_id=_current_user.project_id,
)
if not created_creds:
raise Exception(status_code=500, detail="Failed to create credentials")
logger.error(
f"[create_new_credential] Failed to create credentials | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}"
)
raise HTTPException(status_code=500, detail="Failed to create credentials")

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

Expand All @@ -78,6 +87,9 @@ def read_credential(
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 Down Expand Up @@ -121,6 +133,9 @@ def update_credential(
_current_user: UserProjectOrg = Depends(get_current_user_org_project),
):
if not creds_in or not creds_in.provider or not creds_in.credential:
logger.error(
f"[update_credential] Invalid input | organization_id: {_current_user.organization_id}, project_id: {_current_user.project_id}"
)
raise HTTPException(
status_code=400, detail="Provider and credential must be provided"
)
Expand Down Expand Up @@ -157,6 +172,9 @@ def delete_provider_credential(
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(
Expand Down Expand Up @@ -188,6 +206,9 @@ def delete_all_credentials(
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"
)
Expand Down
27 changes: 26 additions & 1 deletion backend/app/api/routes/users.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import uuid
import logging
from typing import Any

from fastapi import APIRouter, Depends
Expand Down Expand Up @@ -26,6 +26,7 @@
from app.utils import generate_new_account_email, send_email
from app.core.exception_handlers import HTTPException

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/users", tags=["users"])


Expand All @@ -49,6 +50,9 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
)
def create_user_endpoint(*, session: SessionDep, user_in: UserCreate) -> Any:
if get_user_by_email(session=session, email=user_in.email):
logger.error(
f"[create_user_endpoint] Attempting to create user with existing email | email: {user_in.email}"
)
raise HTTPException(
status_code=400,
detail="The user with this email already exists in the system.",
Expand All @@ -75,6 +79,9 @@ def update_user_me(
if user_in.email:
existing_user = get_user_by_email(session=session, email=user_in.email)
if existing_user and existing_user.id != current_user.id:
logger.error(
f"[update_user_me] Attempting to update user with existing email | email: {user_in.email}, user_id: {current_user.id}"
)
raise HTTPException(
status_code=409, detail="User with this email already exists"
)
Expand All @@ -83,6 +90,7 @@ def update_user_me(
session.add(current_user)
session.commit()
session.refresh(current_user)
logger.info(f"[update_user_me] User updated | user_id: {current_user.id}")
return current_user


Expand All @@ -102,6 +110,7 @@ def update_password_me(
current_user.hashed_password = get_password_hash(body.new_password)
session.add(current_user)
session.commit()
logger.info(f"[update_password_me] Password updated | user_id: {current_user.id}")
return Message(message="Password updated successfully")


Expand All @@ -113,11 +122,15 @@ def read_user_me(current_user: CurrentUser) -> Any:
@router.delete("/me", response_model=Message)
def delete_user_me(session: SessionDep, current_user: CurrentUser) -> Any:
if current_user.is_superuser:
logger.error(
f"[delete_user_me] Attempting to delete superuser account by itself | user_id: {current_user.id}"
)
raise HTTPException(
status_code=403, detail="Super users are not allowed to delete themselves"
)
session.delete(current_user)
session.commit()
logger.info(f"[delete_user_me] User deleted | user_id: {current_user.id}")
return Message(message="User deleted successfully")


Expand All @@ -131,6 +144,9 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any:
This endpoint allows the registration of a new user and is accessible only by a superuser.
"""
if get_user_by_email(session=session, email=user_in.email):
logger.error(
f"[register_user] Attempting to create user with existing email | email: {user_in.email}"
)
raise HTTPException(
status_code=400,
detail="The user with this email already exists in the system",
Expand Down Expand Up @@ -171,6 +187,7 @@ def update_user_endpoint(
) -> Any:
db_user = session.get(User, user_id)
if not db_user:
logger.error(f"[update_user_endpoint] User not found | user_id: {user_id}")
raise HTTPException(
status_code=404,
detail="The user with this id does not exist in the system",
Expand All @@ -179,6 +196,9 @@ def update_user_endpoint(
if user_in.email:
existing_user = get_user_by_email(session=session, email=user_in.email)
if existing_user and existing_user.id != user_id:
logger.error(
f"[update_user_endpoint] Attempting to update user with existing email | email: {user_in.email}, user_id: {user_id}"
)
raise HTTPException(
status_code=409, detail="User with this email already exists"
)
Expand All @@ -196,13 +216,18 @@ def delete_user(
) -> Message:
user = session.get(User, user_id)
if not user:
logger.error(f"[delete_user] User not found | user_id: {user_id}")
raise HTTPException(status_code=404, detail="User not found")

if user == current_user:
logger.error(
f"[delete_user] Attempting to delete self by superuser | user_id: {current_user.id}"
)
raise HTTPException(
status_code=403, detail="Super users are not allowed to delete themselves"
)

session.delete(user)
session.commit()
logger.info(f"[delete_user] User deleted | user_id: {user.id}")
return Message(message="User deleted successfully")
9 changes: 9 additions & 0 deletions backend/app/core/providers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import logging
from typing import Dict, List, Optional
from enum import Enum
from dataclasses import dataclass

logger = logging.getLogger(__name__)


class Provider(str, Enum):
"""Enumeration of supported credential providers."""
Expand Down Expand Up @@ -46,6 +49,9 @@ def validate_provider(provider: str) -> Provider:
return Provider(provider.lower())
except ValueError:
supported = ", ".join(p.value for p in Provider)
logger.error(
f"[validate_provider] Unsupported provider | provider: {provider}, supported_providers: {supported}"
)
raise ValueError(
f"Unsupported provider: {provider}. Supported providers are: {supported}"
)
Expand All @@ -67,6 +73,9 @@ def validate_provider_credentials(provider: str, credentials: Dict[str, str]) ->
if missing_fields := [
field for field in required_fields if field not in credentials
]:
logger.error(
f"[validate_provider_credentials] Missing required fields | provider: {provider}, missing_fields: {', '.join(missing_fields)}"
)
raise ValueError(
f"Missing required fields for {provider}: {', '.join(missing_fields)}"
)
Expand Down
10 changes: 6 additions & 4 deletions backend/app/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
from langfuse.decorators import langfuse_context
from openai import OpenAI

logger = logging.getLogger(__name__)


def now():
return datetime.now(timezone.utc).replace(tzinfo=None)


def raise_from_unknown(error: Exception, status_code=500):
warnings.warn(
logger.warning(
'Unexpected exception "{}": {}'.format(
type(error).__name__,
error,
Expand All @@ -31,7 +33,7 @@ def post_callback(url: HttpUrl, payload: BaseModel):
try:
response.raise_for_status()
except RequestException as err:
warnings.warn(f"Callback failure: {err}")
logger.warning(f"Callback failure: {err}")
errno += 1

return not errno
Expand Down Expand Up @@ -67,7 +69,7 @@ def configure_langfuse(credentials: dict) -> tuple[Langfuse, bool]:

return langfuse, True
except Exception as e:
warnings.warn(f"Failed to configure Langfuse: {str(e)}")
logger.error(f"Failed to configure Langfuse: {str(e)}")
return None, False


Expand All @@ -89,5 +91,5 @@ def configure_openai(credentials: dict) -> tuple[OpenAI, bool]:
client = OpenAI(api_key=credentials["api_key"])
return client, True
except Exception as e:
warnings.warn(f"Failed to configure OpenAI client: {str(e)}")
logger.error(f"Failed to configure OpenAI client: {str(e)}")
return None, False
30 changes: 25 additions & 5 deletions backend/app/crud/credentials.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import logging
from typing import Optional, Dict, Any, List, Union
from sqlmodel import Session, select
from sqlalchemy.exc import IntegrityError
from datetime import datetime, timezone

from app.models import Credential, CredsCreate, CredsUpdate
from app.core.providers import (
validate_provider,
validate_provider_credentials,
get_supported_providers,
)
from app.core.security import encrypt_credentials, decrypt_credentials
from app.core.util import now
from app.core.exception_handlers import HTTPException

logger = logging.getLogger(__name__)


def set_creds_for_org(
*, session: Session, creds_add: CredsCreate, organization_id: int, project_id: int
Expand All @@ -21,6 +22,9 @@ def set_creds_for_org(
created_credentials = []

if not creds_add.credential:
logger.error(
f"[set_creds_for_org] No credentials provided | project_id: {project_id}"
)
raise HTTPException(400, "No credentials provided")

for provider, credentials in creds_add.credential.items():
Expand All @@ -47,10 +51,16 @@ def set_creds_for_org(
created_credentials.append(credential)
except IntegrityError as e:
session.rollback()
logger.error(
f"[set_creds_for_org] Integrity error while adding credentials | organization_id {organization_id}, project_id {project_id}, provider {provider}: {str(e)}",
exc_info=True,
)
raise ValueError(
f"Error while adding credentials for provider {provider}: {str(e)}"
)

logger.info(
f"[set_creds_for_org] Successfully created credentials | organization_id {organization_id}, project_id {project_id}"
)
return created_credentials


Expand Down Expand Up @@ -153,6 +163,9 @@ def update_creds_for_org(
)
creds = session.exec(statement).first()
if creds is None:
logger.error(
f"[update_creds_for_org] Credentials not found | organization {org_id}, provider {creds_in.provider}, project_id {project_id}"
)
raise HTTPException(
status_code=404, detail="Credentials not found for this provider"
)
Expand All @@ -162,7 +175,9 @@ def update_creds_for_org(
session.add(creds)
session.commit()
session.refresh(creds)

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


Expand All @@ -186,7 +201,9 @@ def remove_provider_credential(
session.add(creds)
session.commit()
session.refresh(creds)

logger.info(
f"[remove_provider_credential] Successfully removed credentials for provider | organization_id {org_id}, provider {provider}, project_id {project_id}"
)
return creds


Expand All @@ -207,4 +224,7 @@ def remove_creds_for_org(
session.add(cred)

session.commit()
logger.info(
f"[remove_creds_for_org] Successfully removed all the credentials | organization_id {org_id}, project_id {project_id}"
)
return creds
9 changes: 8 additions & 1 deletion backend/app/crud/user.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import uuid
import logging
from typing import Any

from sqlmodel import Session, select

from app.core.security import get_password_hash, verify_password

from app.models import User, UserCreate, UserUpdate

logger = logging.getLogger(__name__)


def create_user(*, session: Session, user_create: UserCreate) -> User:
db_obj = User.model_validate(
Expand All @@ -14,6 +17,7 @@ def create_user(*, session: Session, user_create: UserCreate) -> User:
session.add(db_obj)
session.commit()
session.refresh(db_obj)
logger.info(f"[create_user] User created | user_id: {db_obj.id}")
return db_obj


Expand All @@ -28,6 +32,9 @@ def update_user(*, session: Session, db_user: User, user_in: UserUpdate) -> Any:
session.add(db_user)
session.commit()
session.refresh(db_user)
logger.info(
f"[update_user] User updated | user_id: {db_user.id}, updated_fields: {list(user_data.keys())}"
)
return db_user


Expand Down
2 changes: 1 addition & 1 deletion backend/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def get_openai_client(session: Session, org_id: int, project_id: int) -> OpenAI:
)

if not credentials or "api_key" not in credentials:
logger.warning(
logger.error(
f"[get_openai_client] OpenAI credentials not found. | project_id: {project_id}"
)
raise HTTPException(
Expand Down