Skip to content
Open
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
108 changes: 108 additions & 0 deletions backend/apps/app_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
FastAPI application factory with common configurations and exception handlers.
"""
import logging

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from consts.exceptions import AppException


logger = logging.getLogger(__name__)


def create_app(
title: str = "Nexent API",
description: str = "",
version: str = "1.0.0",
root_path: str = "/api",
cors_origins: list = None,
cors_methods: list = None,
enable_monitoring: bool = True,
) -> FastAPI:
"""
Create a FastAPI application with common configurations.
Args:
title: API title
description: API description
version: API version
root_path: Root path for the API
cors_origins: List of allowed CORS origins (default: ["*"])
cors_methods: List of allowed CORS methods (default: ["*"])
enable_monitoring: Whether to enable monitoring
Returns:
Configured FastAPI application
"""
app = FastAPI(
title=title,
description=description,
version=version,
root_path=root_path
)

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins or ["*"],
allow_credentials=True,
allow_methods=cors_methods or ["*"],
allow_headers=["*"],
)

# Register exception handlers
register_exception_handlers(app)

# Initialize monitoring if enabled
if enable_monitoring:
try:
from utils.monitoring import monitoring_manager
monitoring_manager.setup_fastapi_app(app)
except ImportError:
logger.warning("Monitoring utilities not available")

return app


def register_exception_handlers(app: FastAPI) -> None:
"""
Register common exception handlers for the FastAPI application.
Args:
app: FastAPI application instance
"""

@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
logger.error(f"HTTPException: {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)

@app.exception_handler(AppException)
async def app_exception_handler(request, exc):
logger.error(f"AppException: {exc.error_code.value} - {exc.message}")
return JSONResponse(
status_code=exc.http_status,
content={
"code": exc.error_code.value,
"message": exc.message,
"details": exc.details if exc.details else None
},
)

@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
# Don't catch AppException - it has its own handler
if isinstance(exc, AppException):
return await app_exception_handler(request, exc)

logger.error(f"Generic Exception: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Internal server error, please try again later."},
)
42 changes: 3 additions & 39 deletions backend/apps/config_app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import logging

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from apps.app_factory import create_app
from apps.agent_app import agent_config_router as agent_router
from apps.config_sync_app import router as config_sync_router
from apps.datamate_app import router as datamate_router
Expand All @@ -26,21 +23,11 @@
from apps.invitation_app import router as invitation_router
from consts.const import IS_SPEED_MODE

# Import monitoring utilities
from utils.monitoring import monitoring_manager

# Create logger instance
logger = logging.getLogger("base_app")
app = FastAPI(root_path="/api")

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
# Create FastAPI app with common configurations
app = create_app(title="Nexent Config API", description="Configuration APIs")

app.include_router(model_manager_router)
app.include_router(config_sync_router)
Expand Down Expand Up @@ -69,26 +56,3 @@
app.include_router(group_router)
app.include_router(user_router)
app.include_router(invitation_router)

# Initialize monitoring for the application
monitoring_manager.setup_fastapi_app(app)


# Global exception handler for HTTP exceptions
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
logger.error(f"HTTPException: {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)


# Global exception handler for all uncaught exceptions
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
logger.error(f"Generic Exception: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Internal server error, please try again later."},
)
31 changes: 10 additions & 21 deletions backend/apps/dify_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from fastapi import APIRouter, Header, HTTPException, Query
from fastapi.responses import JSONResponse

from consts.error_code import ErrorCode
from consts.exceptions import AppException
from services.dify_service import fetch_dify_datasets_impl
from utils.auth_utils import get_current_user_id

Expand All @@ -35,18 +37,10 @@ async def fetch_dify_datasets_api(
# Normalize URL by removing trailing slash
dify_api_base = dify_api_base.rstrip('/')
except Exception as e:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)
logger.error(f"Invalid Dify configuration: {e}")
raise AppException(ErrorCode.DIFY_CONFIG_INVALID,
f"Invalid URL format: {str(e)}")

try:
_, tenant_id = get_current_user_id(authorization)
except Exception as e:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)

try:
result = fetch_dify_datasets_impl(
Expand All @@ -57,15 +51,10 @@ async def fetch_dify_datasets_api(
status_code=HTTPStatus.OK,
content=result
)
except ValueError as e:
logger.warning(f"Invalid Dify configuration: {e}")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(e)
)
except AppException:
# Re-raise AppException to be handled by global middleware
raise
except Exception as e:
logger.error(f"Failed to fetch Dify datasets: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)
raise AppException(ErrorCode.DIFY_SERVICE_ERROR,
f"Failed to fetch Dify datasets: {str(e)}")
40 changes: 5 additions & 35 deletions backend/apps/northbound_base_app.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,17 @@
from http import HTTPStatus
import logging
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware

from apps.app_factory import create_app
from .northbound_app import router as northbound_router
from consts.exceptions import LimitExceededError, UnauthorizedError, SignatureValidationError

logger = logging.getLogger("northbound_base_app")


northbound_app = FastAPI(
# Create FastAPI app with common configurations
northbound_app = create_app(
title="Nexent Northbound API",
description="Northbound APIs for partners",
version="1.0.0",
root_path="/api"
)

northbound_app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
cors_methods=["GET", "POST", "PUT", "DELETE"],
enable_monitoring=False # Disable monitoring for northbound API if not needed
)


northbound_app.include_router(northbound_router)


@northbound_app.exception_handler(HTTPException)
async def northbound_http_exception_handler(request, exc):
logger.error(f"Northbound HTTPException: {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)


@northbound_app.exception_handler(Exception)
async def northbound_generic_exception_handler(request, exc):
logger.error(f"Northbound Generic Exception: {exc}")
return JSONResponse(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
content={"message": "Internal server error, please try again later."},
)
48 changes: 7 additions & 41 deletions backend/apps/runtime_app.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,24 @@
import logging

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from apps.app_factory import create_app
from apps.agent_app import agent_runtime_router as agent_router
from apps.voice_app import voice_runtime_router as voice_router
from apps.conversation_management_app import router as conversation_management_router
from apps.memory_config_app import router as memory_config_router
from apps.file_management_app import file_management_runtime_router as file_management_router

# Import monitoring utilities
from utils.monitoring import monitoring_manager
from middleware.exception_handler import ExceptionHandlerMiddleware

# Create logger instance
logger = logging.getLogger("runtime_app")
app = FastAPI(root_path="/api")

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Create FastAPI app with common configurations
app = create_app(title="Nexent Runtime API", description="Runtime APIs")

# Add global exception handler middleware
app.add_middleware(ExceptionHandlerMiddleware)

app.include_router(agent_router)
app.include_router(conversation_management_router)
app.include_router(memory_config_router)
app.include_router(file_management_router)
app.include_router(voice_router)

# Initialize monitoring for the application
monitoring_manager.setup_fastapi_app(app)


# Global exception handler for HTTP exceptions
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
logger.error(f"HTTPException: {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)


# Global exception handler for all uncaught exceptions
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
logger.error(f"Generic Exception: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Internal server error, please try again later."},
)


Loading
Loading