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
109 changes: 0 additions & 109 deletions backend/app/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
AuthContext,
TokenPayload,
User,
UserOrganization,
UserProjectOrg,
)


Expand All @@ -37,113 +35,6 @@ def get_db() -> Generator[Session, None, None]:
TokenDep = Annotated[str, Depends(reusable_oauth2)]


def get_current_user(
session: SessionDep,
token: TokenDep,
api_key: Annotated[str, Depends(api_key_header)],
) -> User:
"""Authenticate user via API Key first, fallback to JWT token. Returns only User."""

if api_key:
api_key_record = api_key_manager.verify(session, api_key)
if not api_key_record:
raise HTTPException(status_code=401, detail="Invalid API Key")

if not api_key_record.user.is_active:
raise HTTPException(status_code=403, detail="Inactive user")

return api_key_record.user # Return only User object

elif token:
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
)
token_data = TokenPayload(**payload)
except (InvalidTokenError, ValidationError):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Could not validate credentials",
)

user = session.get(User, token_data.sub)
if not user:
raise HTTPException(status_code=404, detail="User not found")
if not user.is_active:
raise HTTPException(status_code=403, detail="Inactive user")

return user # Return only User object

raise HTTPException(status_code=401, detail="Invalid Authorization format")


CurrentUser = Annotated[User, Depends(get_current_user)]


def get_current_user_org(
current_user: CurrentUser, session: SessionDep, request: Request
) -> UserOrganization:
"""Extend `User` with organization_id if available, otherwise return UserOrganization without it."""

organization_id = None
api_key = request.headers.get("X-API-KEY")
if api_key:
api_key_record = api_key_manager.verify(session, api_key)
if api_key_record:
validate_organization(session, api_key_record.organization.id)
organization_id = api_key_record.organization.id

return UserOrganization(
**current_user.model_dump(), organization_id=organization_id
)


CurrentUserOrg = Annotated[UserOrganization, Depends(get_current_user_org)]


def get_current_user_org_project(
current_user: CurrentUser, session: SessionDep, request: Request
) -> UserProjectOrg:
api_key = request.headers.get("X-API-KEY")
organization_id = None
project_id = None

if api_key:
api_key_record = api_key_manager.verify(session, api_key)
if api_key_record:
validate_organization(session, api_key_record.organization.id)
organization_id = api_key_record.organization.id
project_id = api_key_record.project.id

else:
raise HTTPException(status_code=401, detail="Invalid API Key")

return UserProjectOrg(
**current_user.model_dump(),
organization_id=organization_id,
project_id=project_id,
)


CurrentUserOrgProject = Annotated[UserProjectOrg, Depends(get_current_user_org_project)]


def get_current_active_superuser(current_user: CurrentUser) -> User:
if not current_user.is_superuser:
raise HTTPException(
status_code=403, detail="The user doesn't have enough privileges"
)
return current_user


def get_current_active_superuser_org(current_user: CurrentUserOrg) -> User:
if not current_user.is_superuser:
raise HTTPException(
status_code=403, detail="The user doesn't have enough privileges"
)
return current_user


def get_auth_context(
session: SessionDep,
token: TokenDep,
Expand Down
4 changes: 2 additions & 2 deletions backend/app/api/routes/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def list_api_keys_route(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=100, description="Maximum records to return"),
):
crud = APIKeyCrud(session, current_user.project.id)
crud = APIKeyCrud(session, current_user.project_.id)
api_keys = crud.read_all(skip=skip, limit=limit)

return APIResponse.success_response(api_keys)
Expand All @@ -67,7 +67,7 @@ def delete_api_key_route(
current_user: AuthContextDep,
session: SessionDep,
):
api_key_crud = APIKeyCrud(session=session, project_id=current_user.project.id)
api_key_crud = APIKeyCrud(session=session, project_id=current_user.project_.id)
api_key_crud.delete(key_id=key_id)

return APIResponse.success_response(Message(message="API Key deleted successfully"))
76 changes: 46 additions & 30 deletions backend/app/api/routes/assistants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import Annotated

from fastapi import APIRouter, Depends, Path, HTTPException, Query
from sqlmodel import Session

from app.api.deps import get_db, get_current_user_org_project
from app.api.deps import AuthContextDep, SessionDep
from app.crud import (
fetch_assistant_from_openai,
sync_assistant,
Expand All @@ -13,7 +12,8 @@
get_assistants_by_project,
delete_assistant,
)
from app.models import UserProjectOrg, AssistantCreate, AssistantUpdate, Assistant
from app.models import AssistantCreate, AssistantUpdate, Assistant
from app.api.permissions import Permission, require_permission
from app.utils import APIResponse, get_openai_client

router = APIRouter(prefix="/assistant", tags=["Assistants"])
Expand All @@ -23,71 +23,81 @@
"/{assistant_id}/ingest",
response_model=APIResponse[Assistant],
status_code=201,
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def ingest_assistant_route(
assistant_id: Annotated[str, Path(description="The ID of the assistant to ingest")],
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
session: SessionDep,
current_user: AuthContextDep,
):
"""
Ingest an assistant from OpenAI and store it in the platform.
"""

client = get_openai_client(
session, current_user.organization_id, current_user.project_id
session, current_user.organization_.id, current_user.project_.id
)

openai_assistant = fetch_assistant_from_openai(assistant_id, client)
assistant = sync_assistant(
session=session,
organization_id=current_user.organization_id,
project_id=current_user.project_id,
organization_id=current_user.organization_.id,
project_id=current_user.project_.id,
openai_assistant=openai_assistant,
)

return APIResponse.success_response(assistant)


@router.post("/", response_model=APIResponse[Assistant], status_code=201)
@router.post(
"/",
response_model=APIResponse[Assistant],
status_code=201,
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def create_assistant_route(
assistant_in: AssistantCreate,
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
session: SessionDep,
current_user: AuthContextDep,
):
"""
Create a new assistant in the local DB, checking that vector store IDs exist in OpenAI first.
"""
client = get_openai_client(
session, current_user.organization_id, current_user.project_id
session, current_user.organization_.id, current_user.project_.id
)
assistant = create_assistant(
session=session,
openai_client=client,
assistant=assistant_in,
project_id=current_user.project_id,
organization_id=current_user.organization_id,
project_id=current_user.project_.id,
organization_id=current_user.organization_.id,
)
return APIResponse.success_response(assistant)


@router.patch("/{assistant_id}", response_model=APIResponse[Assistant])
@router.patch(
"/{assistant_id}",
response_model=APIResponse[Assistant],
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def update_assistant_route(
assistant_id: Annotated[str, Path(description="Assistant ID to update")],
assistant_update: AssistantUpdate,
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
session: SessionDep,
current_user: AuthContextDep,
):
"""
Update an existing assistant with provided fields. Supports replacing, adding, or removing vector store IDs.
"""
client = get_openai_client(
session, current_user.organization_id, current_user.project_id
session, current_user.organization_.id, current_user.project_.id
)
updated_assistant = update_assistant(
session=session,
assistant_id=assistant_id,
openai_client=client,
project_id=current_user.project_id,
project_id=current_user.project_.id,
assistant_update=assistant_update,
)
return APIResponse.success_response(updated_assistant)
Expand All @@ -97,16 +107,17 @@ def update_assistant_route(
"/{assistant_id}",
response_model=APIResponse[Assistant],
summary="Get a single assistant by its ID",
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def get_assistant_route(
assistant_id: str = Path(..., description="The assistant_id to fetch"),
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
assistant_id: Annotated[str, Path(description="The assistant_id to fetch")],
session: SessionDep,
current_user: AuthContextDep,
):
"""
Fetch a single assistant by its assistant_id.
"""
assistant = get_assistant_by_id(session, assistant_id, current_user.project_id)
assistant = get_assistant_by_id(session, assistant_id, current_user.project_.id)
if not assistant:
raise HTTPException(
status_code=404, detail=f"Assistant with ID {assistant_id} not found."
Expand All @@ -118,10 +129,11 @@ def get_assistant_route(
"/",
response_model=APIResponse[list[Assistant]],
summary="List all assistants in the current project",
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def list_assistants_route(
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
session: SessionDep,
current_user: AuthContextDep,
skip: int = Query(0, ge=0, description="How many items to skip"),
limit: int = Query(100, ge=1, le=100, description="Maximum items to return"),
):
Expand All @@ -130,24 +142,28 @@ def list_assistants_route(
"""

assistants = get_assistants_by_project(
session=session, project_id=current_user.project_id, skip=skip, limit=limit
session=session, project_id=current_user.project_.id, skip=skip, limit=limit
)
return APIResponse.success_response(assistants)


@router.delete("/{assistant_id}", response_model=APIResponse)
@router.delete(
"/{assistant_id}",
response_model=APIResponse,
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def delete_assistant_route(
assistant_id: Annotated[str, Path(description="Assistant ID to delete")],
session: Session = Depends(get_db),
current_user: UserProjectOrg = Depends(get_current_user_org_project),
session: SessionDep,
current_user: AuthContextDep,
):
"""
Soft delete an assistant by marking it as deleted.
"""
delete_assistant(
session=session,
assistant_id=assistant_id,
project_id=current_user.project_id,
project_id=current_user.project_.id,
)
return APIResponse.success_response(
data={"message": "Assistant deleted successfully."}
Expand Down
12 changes: 7 additions & 5 deletions backend/app/api/routes/collection_job.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging
from uuid import UUID

from fastapi import APIRouter
from fastapi import APIRouter, Depends
from fastapi import Path as FastPath


from app.api.deps import SessionDep, CurrentUserOrgProject
from app.api.deps import AuthContextDep, SessionDep
from app.api.permissions import Permission, require_permission
from app.crud import (
CollectionCrud,
CollectionJobCrud,
Expand All @@ -29,13 +30,14 @@
"/jobs/{job_id}",
description=load_description("collections/job_info.md"),
response_model=APIResponse[CollectionJobPublic],
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def collection_job_info(
session: SessionDep,
current_user: CurrentUserOrgProject,
current_user: AuthContextDep,
job_id: UUID = FastPath(description="Collection job to retrieve"),
):
collection_job_crud = CollectionJobCrud(session, current_user.project_id)
collection_job_crud = CollectionJobCrud(session, current_user.project_.id)
collection_job = collection_job_crud.read_one(job_id)

job_out = CollectionJobPublic.model_validate(collection_job)
Expand All @@ -45,7 +47,7 @@ def collection_job_info(
collection_job.action_type == CollectionActionType.CREATE
and collection_job.status == CollectionJobStatus.SUCCESSFUL
):
collection_crud = CollectionCrud(session, current_user.project_id)
collection_crud = CollectionCrud(session, current_user.project_.id)
collection = collection_crud.read_one(collection_job.collection_id)
job_out.collection = CollectionPublic.model_validate(collection)

Expand Down
Loading