mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-03-29 11:32:42 +00:00
Compare commits
1 Commits
cli/v0.2.0
...
nikg/std-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b596e179 |
@@ -5,7 +5,6 @@ from uuid import UUID
|
||||
import httpx
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from fastapi import HTTPException
|
||||
from fastapi import Request
|
||||
from fastapi import Response
|
||||
from fastapi.responses import RedirectResponse
|
||||
@@ -24,6 +23,8 @@ from onyx.db.enums import SharingScope
|
||||
from onyx.db.index_attempt import get_latest_index_attempt_for_cc_pair_id
|
||||
from onyx.db.models import BuildSession
|
||||
from onyx.db.models import User
|
||||
from onyx.error_handling.error_codes import OnyxErrorCode
|
||||
from onyx.error_handling.exceptions import OnyxError
|
||||
from onyx.server.features.build.api.messages_api import router as messages_router
|
||||
from onyx.server.features.build.api.models import BuildConnectorInfo
|
||||
from onyx.server.features.build.api.models import BuildConnectorListResponse
|
||||
@@ -44,13 +45,10 @@ logger = setup_logger()
|
||||
def require_onyx_craft_enabled(user: User = Depends(current_user)) -> User:
|
||||
"""
|
||||
Dependency that checks if Onyx Craft is enabled for the user.
|
||||
Raises HTTP 403 if Onyx Craft is disabled via feature flag.
|
||||
Raises OnyxError(UNAUTHORIZED) if Onyx Craft is disabled via feature flag.
|
||||
"""
|
||||
if not is_onyx_craft_enabled(user):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Onyx Craft is not available",
|
||||
)
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Onyx Craft is not available")
|
||||
return user
|
||||
|
||||
|
||||
@@ -290,20 +288,20 @@ def _get_sandbox_url(session_id: UUID, db_session: Session) -> str:
|
||||
Internal URL to proxy requests to
|
||||
|
||||
Raises:
|
||||
HTTPException: If session not found, port not allocated, or sandbox not found
|
||||
OnyxError: If session not found, port not allocated, or sandbox not found
|
||||
"""
|
||||
|
||||
session = db_session.get(BuildSession, session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
if session.nextjs_port is None:
|
||||
raise HTTPException(status_code=503, detail="Session port not allocated")
|
||||
raise OnyxError(OnyxErrorCode.SERVICE_UNAVAILABLE, "Session port not allocated")
|
||||
if session.user_id is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
raise OnyxError(OnyxErrorCode.USER_NOT_FOUND, "User not found")
|
||||
|
||||
sandbox = get_sandbox_by_user_id(db_session, session.user_id)
|
||||
if sandbox is None:
|
||||
raise HTTPException(status_code=404, detail="Sandbox not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Sandbox not found")
|
||||
|
||||
sandbox_manager = get_sandbox_manager()
|
||||
return sandbox_manager.get_webapp_url(sandbox.id, session.nextjs_port)
|
||||
@@ -364,10 +362,10 @@ def _proxy_request(
|
||||
|
||||
except httpx.TimeoutException:
|
||||
logger.error(f"Timeout while proxying request to {target_url}")
|
||||
raise HTTPException(status_code=504, detail="Gateway timeout")
|
||||
raise OnyxError(OnyxErrorCode.GATEWAY_TIMEOUT, "Gateway timeout")
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"Error proxying request to {target_url}: {e}")
|
||||
raise HTTPException(status_code=502, detail="Bad gateway")
|
||||
raise OnyxError(OnyxErrorCode.BAD_GATEWAY, "Bad gateway")
|
||||
|
||||
|
||||
def _check_webapp_access(
|
||||
@@ -381,13 +379,13 @@ def _check_webapp_access(
|
||||
"""
|
||||
session = db_session.get(BuildSession, session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
if session.sharing_scope == SharingScope.PUBLIC_GLOBAL:
|
||||
return session
|
||||
if user is None:
|
||||
raise HTTPException(status_code=401, detail="Authentication required")
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHENTICATED, "Authentication required")
|
||||
if session.sharing_scope == SharingScope.PRIVATE and session.user_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
return session
|
||||
|
||||
|
||||
@@ -427,13 +425,13 @@ def get_webapp(
|
||||
"""
|
||||
try:
|
||||
_check_webapp_access(session_id, user, db_session)
|
||||
except HTTPException as e:
|
||||
except OnyxError as e:
|
||||
if e.status_code == 401:
|
||||
return RedirectResponse(url="/auth/login", status_code=302)
|
||||
raise
|
||||
try:
|
||||
return _proxy_request(path, request, session_id, db_session)
|
||||
except HTTPException as e:
|
||||
except OnyxError as e:
|
||||
if e.status_code in (502, 503, 504):
|
||||
return _offline_html_response()
|
||||
raise
|
||||
@@ -462,19 +460,13 @@ def reset_sandbox(
|
||||
try:
|
||||
success = session_manager.terminate_user_sandbox(user.id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="No sandbox found for user",
|
||||
)
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "No sandbox found for user")
|
||||
db_session.commit()
|
||||
except HTTPException:
|
||||
except OnyxError:
|
||||
raise
|
||||
except Exception as e:
|
||||
db_session.rollback()
|
||||
logger.error(f"Failed to reset sandbox for user {user.id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to reset sandbox: {e}",
|
||||
)
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, f"Failed to reset sandbox: {e}")
|
||||
|
||||
return Response(status_code=204)
|
||||
|
||||
@@ -5,7 +5,6 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -14,6 +13,8 @@ from onyx.configs.constants import PUBLIC_API_TAGS
|
||||
from onyx.db.engine.sql_engine import get_session
|
||||
from onyx.db.engine.sql_engine import get_session_with_current_tenant
|
||||
from onyx.db.models import User
|
||||
from onyx.error_handling.error_codes import OnyxErrorCode
|
||||
from onyx.error_handling.exceptions import OnyxError
|
||||
from onyx.server.features.build.api.models import MessageListResponse
|
||||
from onyx.server.features.build.api.models import MessageRequest
|
||||
from onyx.server.features.build.api.models import MessageResponse
|
||||
@@ -36,7 +37,7 @@ def check_build_rate_limits(
|
||||
"""
|
||||
Dependency to check build mode rate limits before processing the request.
|
||||
|
||||
Raises HTTPException(429) if rate limit is exceeded.
|
||||
Raises OnyxError(RATE_LIMITED) if rate limit is exceeded.
|
||||
Follows the same pattern as chat's check_token_rate_limits.
|
||||
"""
|
||||
session_manager = SessionManager(db_session)
|
||||
@@ -44,10 +45,7 @@ def check_build_rate_limits(
|
||||
try:
|
||||
session_manager.check_rate_limit(user)
|
||||
except RateLimitError as e:
|
||||
raise HTTPException(
|
||||
status_code=429,
|
||||
detail=str(e),
|
||||
)
|
||||
raise OnyxError(OnyxErrorCode.RATE_LIMITED, str(e))
|
||||
|
||||
|
||||
@router.get("/sessions/{session_id}/messages", tags=PUBLIC_API_TAGS)
|
||||
@@ -58,14 +56,14 @@ def list_messages(
|
||||
) -> MessageListResponse:
|
||||
"""Get all messages for a build session."""
|
||||
if user is None:
|
||||
raise HTTPException(status_code=401, detail="Authentication required")
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHENTICATED, "Authentication required")
|
||||
|
||||
session_manager = SessionManager(db_session)
|
||||
|
||||
messages = session_manager.list_messages(session_id, user.id)
|
||||
|
||||
if messages is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return MessageListResponse(
|
||||
messages=[MessageResponse.from_model(msg) for msg in messages]
|
||||
|
||||
@@ -5,7 +5,6 @@ from uuid import UUID
|
||||
from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from fastapi import File
|
||||
from fastapi import HTTPException
|
||||
from fastapi import Response
|
||||
from fastapi import UploadFile
|
||||
from sqlalchemy import exists
|
||||
@@ -17,6 +16,8 @@ from onyx.db.enums import BuildSessionStatus
|
||||
from onyx.db.enums import SandboxStatus
|
||||
from onyx.db.models import BuildMessage
|
||||
from onyx.db.models import User
|
||||
from onyx.error_handling.error_codes import OnyxErrorCode
|
||||
from onyx.error_handling.exceptions import OnyxError
|
||||
from onyx.redis.redis_pool import get_redis_client
|
||||
from onyx.server.features.build.api.models import ArtifactResponse
|
||||
from onyx.server.features.build.api.models import DetailedSessionResponse
|
||||
@@ -117,9 +118,9 @@ def create_session(
|
||||
blocking=True, blocking_timeout=SESSION_CREATE_LOCK_TIMEOUT_SECONDS
|
||||
)
|
||||
if not acquired:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Session creation timed out waiting for lock",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.SERVICE_UNAVAILABLE,
|
||||
"Session creation timed out waiting for lock",
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -144,11 +145,11 @@ def create_session(
|
||||
except ValueError as e:
|
||||
logger.exception("Session creation failed")
|
||||
db_session.rollback()
|
||||
raise HTTPException(status_code=429, detail=str(e))
|
||||
raise OnyxError(OnyxErrorCode.RATE_LIMITED, str(e))
|
||||
except Exception as e:
|
||||
db_session.rollback()
|
||||
logger.error(f"Session creation failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Session creation failed: {e}")
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, f"Session creation failed: {e}")
|
||||
finally:
|
||||
if lock.owned():
|
||||
lock.release()
|
||||
@@ -171,7 +172,7 @@ def get_session_details(
|
||||
session = session_manager.get_session(session_id, user.id)
|
||||
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
# Get the user's sandbox to include in response
|
||||
sandbox = get_sandbox_by_user_id(db_session, user.id)
|
||||
@@ -237,7 +238,7 @@ def generate_session_name(
|
||||
generated_name = session_manager.generate_session_name(session_id, user.id)
|
||||
|
||||
if generated_name is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return SessionNameGenerateResponse(name=generated_name)
|
||||
|
||||
@@ -257,7 +258,7 @@ def generate_suggestions(
|
||||
# Verify session exists and belongs to user
|
||||
session = session_manager.get_session(session_id, user.id)
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
# Generate suggestions
|
||||
suggestions_data = session_manager.generate_followup_suggestions(
|
||||
@@ -290,7 +291,7 @@ def update_session_name(
|
||||
session = session_manager.update_session_name(session_id, user.id, request.name)
|
||||
|
||||
if session is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
# Get the user's sandbox to include in response
|
||||
sandbox = get_sandbox_by_user_id(db_session, user.id)
|
||||
@@ -309,7 +310,7 @@ def set_session_public(
|
||||
session_id, user.id, request.sharing_scope, db_session
|
||||
)
|
||||
if not updated:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
return SetSessionSharingResponse(
|
||||
session_id=str(session_id),
|
||||
sharing_scope=updated.sharing_scope,
|
||||
@@ -332,18 +333,18 @@ def delete_session(
|
||||
try:
|
||||
success = session_manager.delete_session(session_id, user.id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
db_session.commit()
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions (like 404) without rollback
|
||||
except OnyxError:
|
||||
# Re-raise OnyxError exceptions (like 404) without rollback
|
||||
raise
|
||||
except Exception as e:
|
||||
# Sandbox termination failed - rollback to preserve session
|
||||
db_session.rollback()
|
||||
logger.error(f"Failed to delete session {session_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to delete session: {e}",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.INTERNAL_ERROR,
|
||||
f"Failed to delete session: {e}",
|
||||
)
|
||||
|
||||
return Response(status_code=204)
|
||||
@@ -373,11 +374,11 @@ def restore_session(
|
||||
"""
|
||||
session = get_build_session(session_id, user.id, db_session)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
sandbox = get_sandbox_by_user_id(db_session, user.id)
|
||||
if not sandbox:
|
||||
raise HTTPException(status_code=404, detail="Sandbox not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Sandbox not found")
|
||||
|
||||
# If sandbox is already running, check if session workspace exists
|
||||
sandbox_manager = get_sandbox_manager()
|
||||
@@ -392,10 +393,7 @@ def restore_session(
|
||||
# instead of making the user wait. The frontend will retry.
|
||||
acquired = lock.acquire(blocking=False)
|
||||
if not acquired:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Restore already in progress",
|
||||
)
|
||||
raise OnyxError(OnyxErrorCode.CONFLICT, "Restore already in progress")
|
||||
|
||||
try:
|
||||
# Re-fetch sandbox status (may have changed while waiting for lock)
|
||||
@@ -510,9 +508,9 @@ def restore_session(
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to restore session {session_id}: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to restore session: {e}",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.INTERNAL_ERROR,
|
||||
f"Failed to restore session: {e}",
|
||||
)
|
||||
finally:
|
||||
if lock.owned():
|
||||
@@ -547,7 +545,7 @@ def list_artifacts(
|
||||
|
||||
artifacts = session_manager.list_artifacts(session_id, user_id)
|
||||
if artifacts is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return artifacts
|
||||
|
||||
@@ -577,15 +575,15 @@ def list_directory(
|
||||
except ValueError as e:
|
||||
error_message = str(e)
|
||||
if "path traversal" in error_message.lower():
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
elif "not found" in error_message.lower():
|
||||
raise HTTPException(status_code=404, detail="Directory not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Directory not found")
|
||||
elif "not a directory" in error_message.lower():
|
||||
raise HTTPException(status_code=400, detail="Path is not a directory")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Path is not a directory")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if listing is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return listing
|
||||
|
||||
@@ -609,13 +607,13 @@ def download_artifact(
|
||||
"path traversal" in error_message.lower()
|
||||
or "access denied" in error_message.lower()
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
elif "directory" in error_message.lower():
|
||||
raise HTTPException(status_code=400, detail="Cannot download directory")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Cannot download directory")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Artifact not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Artifact not found")
|
||||
|
||||
content, mime_type, filename = result
|
||||
|
||||
@@ -659,11 +657,11 @@ def export_docx(
|
||||
"path traversal" in error_message.lower()
|
||||
or "access denied" in error_message.lower()
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "File not found")
|
||||
|
||||
docx_bytes, filename = result
|
||||
|
||||
@@ -701,11 +699,11 @@ def get_pptx_preview(
|
||||
"path traversal" in error_message.lower()
|
||||
or "access denied" in error_message.lower()
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return PptxPreviewResponse(**result)
|
||||
|
||||
@@ -727,7 +725,7 @@ def get_webapp_info(
|
||||
webapp_info = session_manager.get_webapp_info(session_id, user_id)
|
||||
|
||||
if webapp_info is None:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise OnyxError(OnyxErrorCode.SESSION_NOT_FOUND, "Session not found")
|
||||
|
||||
return WebappInfo(**webapp_info)
|
||||
|
||||
@@ -749,7 +747,7 @@ def download_webapp(
|
||||
result = session_manager.download_webapp_zip(session_id, user_id)
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Webapp not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Webapp not found")
|
||||
|
||||
zip_bytes, filename = result
|
||||
|
||||
@@ -782,11 +780,11 @@ def download_directory(
|
||||
except ValueError as e:
|
||||
error_message = str(e)
|
||||
if "path traversal" in error_message.lower():
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Directory not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "Directory not found")
|
||||
|
||||
zip_bytes, filename = result
|
||||
|
||||
@@ -814,7 +812,7 @@ def upload_file_endpoint(
|
||||
session_manager = SessionManager(db_session)
|
||||
|
||||
if not file.filename:
|
||||
raise HTTPException(status_code=400, detail="File has no filename")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "File has no filename")
|
||||
|
||||
# Read file content (use sync file interface)
|
||||
content = file.file.read()
|
||||
@@ -822,7 +820,7 @@ def upload_file_endpoint(
|
||||
# Validate file (extension, mime type, size)
|
||||
is_valid, error = validate_file(file.filename, file.content_type, len(content))
|
||||
if not is_valid:
|
||||
raise HTTPException(status_code=400, detail=error)
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error)
|
||||
|
||||
# Sanitize filename
|
||||
safe_filename = sanitize_filename(file.filename)
|
||||
@@ -836,12 +834,12 @@ def upload_file_endpoint(
|
||||
)
|
||||
except UploadLimitExceededError as e:
|
||||
# Return 429 for limit exceeded errors
|
||||
raise HTTPException(status_code=429, detail=str(e))
|
||||
raise OnyxError(OnyxErrorCode.RATE_LIMITED, str(e))
|
||||
except ValueError as e:
|
||||
error_message = str(e)
|
||||
if "not found" in error_message.lower():
|
||||
raise HTTPException(status_code=404, detail=error_message)
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, error_message)
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
return UploadResponse(
|
||||
filename=safe_filename,
|
||||
@@ -871,14 +869,14 @@ def delete_file_endpoint(
|
||||
except ValueError as e:
|
||||
error_message = str(e)
|
||||
if "path traversal" in error_message.lower():
|
||||
raise HTTPException(status_code=403, detail="Access denied")
|
||||
raise OnyxError(OnyxErrorCode.UNAUTHORIZED, "Access denied")
|
||||
elif "not found" in error_message.lower():
|
||||
raise HTTPException(status_code=404, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, error_message)
|
||||
elif "directory" in error_message.lower():
|
||||
raise HTTPException(status_code=400, detail="Cannot delete directory")
|
||||
raise HTTPException(status_code=400, detail=error_message)
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Cannot delete directory")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, error_message)
|
||||
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "File not found")
|
||||
|
||||
return Response(status_code=204)
|
||||
|
||||
@@ -32,7 +32,6 @@ from fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from fastapi import File
|
||||
from fastapi import Form
|
||||
from fastapi import HTTPException
|
||||
from fastapi import Query
|
||||
from fastapi import UploadFile
|
||||
from pydantic import BaseModel
|
||||
@@ -50,6 +49,8 @@ from onyx.db.engine.sql_engine import get_session
|
||||
from onyx.db.enums import ConnectorCredentialPairStatus
|
||||
from onyx.db.models import User
|
||||
from onyx.document_index.interfaces import DocumentMetadata
|
||||
from onyx.error_handling.error_codes import OnyxErrorCode
|
||||
from onyx.error_handling.exceptions import OnyxError
|
||||
from onyx.server.features.build.configs import USER_LIBRARY_MAX_FILE_SIZE_BYTES
|
||||
from onyx.server.features.build.configs import USER_LIBRARY_MAX_FILES_PER_UPLOAD
|
||||
from onyx.server.features.build.configs import USER_LIBRARY_MAX_TOTAL_SIZE_BYTES
|
||||
@@ -184,12 +185,12 @@ def _validate_zip_contents(
|
||||
"""Validate zip file contents before extraction.
|
||||
|
||||
Checks file count limit and total decompressed size against storage quota.
|
||||
Raises HTTPException on validation failure.
|
||||
Raises OnyxError on validation failure.
|
||||
"""
|
||||
if len(zip_file.namelist()) > USER_LIBRARY_MAX_FILES_PER_UPLOAD:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Zip contains too many files. Maximum is {USER_LIBRARY_MAX_FILES_PER_UPLOAD}.",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"Zip contains too many files. Maximum is {USER_LIBRARY_MAX_FILES_PER_UPLOAD}.",
|
||||
)
|
||||
|
||||
# Zip bomb protection: check total decompressed size before extracting
|
||||
@@ -197,9 +198,9 @@ def _validate_zip_contents(
|
||||
info.file_size for info in zip_file.infolist() if not info.is_dir()
|
||||
)
|
||||
if existing_usage + declared_total > USER_LIBRARY_MAX_TOTAL_SIZE_BYTES:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
(
|
||||
f"Zip decompressed size ({declared_total // (1024*1024)}MB) "
|
||||
f"would exceed storage limit."
|
||||
),
|
||||
@@ -213,19 +214,19 @@ def _verify_ownership_and_get_document(
|
||||
) -> Any:
|
||||
"""Verify the user owns the document and return it.
|
||||
|
||||
Raises HTTPException on authorization failure or if document not found.
|
||||
Raises OnyxError on authorization failure or if document not found.
|
||||
"""
|
||||
from onyx.db.document import get_document
|
||||
|
||||
user_prefix = f"CRAFT_FILE__{user.id}__"
|
||||
if not document_id.startswith(user_prefix):
|
||||
raise HTTPException(
|
||||
status_code=403, detail="Not authorized to modify this file"
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.UNAUTHORIZED, "Not authorized to modify this file"
|
||||
)
|
||||
|
||||
doc = get_document(document_id, db_session)
|
||||
if doc is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
raise OnyxError(OnyxErrorCode.NOT_FOUND, "File not found")
|
||||
|
||||
return doc
|
||||
|
||||
@@ -333,13 +334,13 @@ async def upload_files(
|
||||
"""
|
||||
tenant_id = get_current_tenant_id()
|
||||
if tenant_id is None:
|
||||
raise HTTPException(status_code=500, detail="Tenant ID not found")
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, "Tenant ID not found")
|
||||
|
||||
# Validate file count
|
||||
if len(files) > USER_LIBRARY_MAX_FILES_PER_UPLOAD:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Too many files. Maximum is {USER_LIBRARY_MAX_FILES_PER_UPLOAD} per upload.",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"Too many files. Maximum is {USER_LIBRARY_MAX_FILES_PER_UPLOAD} per upload.",
|
||||
)
|
||||
|
||||
# Check cumulative storage usage
|
||||
@@ -370,17 +371,17 @@ async def upload_files(
|
||||
|
||||
# Validate individual file size
|
||||
if file_size > USER_LIBRARY_MAX_FILE_SIZE_BYTES:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"File '{file.filename}' exceeds maximum size of {USER_LIBRARY_MAX_FILE_SIZE_BYTES // (1024*1024)}MB",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"File '{file.filename}' exceeds maximum size of {USER_LIBRARY_MAX_FILE_SIZE_BYTES // (1024*1024)}MB",
|
||||
)
|
||||
|
||||
# Validate cumulative storage (existing + this upload batch)
|
||||
total_size += file_size
|
||||
if existing_usage + total_size > USER_LIBRARY_MAX_TOTAL_SIZE_BYTES:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Total storage would exceed maximum of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"Total storage would exceed maximum of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
)
|
||||
|
||||
# Sanitize filename
|
||||
@@ -449,14 +450,14 @@ async def upload_zip(
|
||||
"""
|
||||
tenant_id = get_current_tenant_id()
|
||||
if tenant_id is None:
|
||||
raise HTTPException(status_code=500, detail="Tenant ID not found")
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, "Tenant ID not found")
|
||||
|
||||
# Read zip content
|
||||
content = await file.read()
|
||||
if len(content) > USER_LIBRARY_MAX_TOTAL_SIZE_BYTES:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Zip file exceeds maximum size of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"Zip file exceeds maximum size of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
)
|
||||
|
||||
# Check cumulative storage usage
|
||||
@@ -515,9 +516,9 @@ async def upload_zip(
|
||||
|
||||
# Validate cumulative storage
|
||||
if existing_usage + total_size > USER_LIBRARY_MAX_TOTAL_SIZE_BYTES:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Total storage would exceed maximum of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
raise OnyxError(
|
||||
OnyxErrorCode.VALIDATION_ERROR,
|
||||
f"Total storage would exceed maximum of {USER_LIBRARY_MAX_TOTAL_SIZE_BYTES // (1024*1024*1024)}GB",
|
||||
)
|
||||
|
||||
# Build path preserving zip structure
|
||||
@@ -560,7 +561,7 @@ async def upload_zip(
|
||||
)
|
||||
|
||||
except zipfile.BadZipFile:
|
||||
raise HTTPException(status_code=400, detail="Invalid zip file")
|
||||
raise OnyxError(OnyxErrorCode.VALIDATION_ERROR, "Invalid zip file")
|
||||
|
||||
# Create directory document records so they appear in the tree view
|
||||
if directory_paths:
|
||||
@@ -674,7 +675,7 @@ def toggle_file_sync(
|
||||
|
||||
tenant_id = get_current_tenant_id()
|
||||
if tenant_id is None:
|
||||
raise HTTPException(status_code=500, detail="Tenant ID not found")
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, "Tenant ID not found")
|
||||
|
||||
doc = _verify_ownership_and_get_document(document_id, user, db_session)
|
||||
|
||||
@@ -719,7 +720,7 @@ def delete_file(
|
||||
|
||||
tenant_id = get_current_tenant_id()
|
||||
if tenant_id is None:
|
||||
raise HTTPException(status_code=500, detail="Tenant ID not found")
|
||||
raise OnyxError(OnyxErrorCode.INTERNAL_ERROR, "Tenant ID not found")
|
||||
|
||||
doc = _verify_ownership_and_get_document(document_id, user, db_session)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user