312a646b89
- Drop is_builtin column from tool_types table - Remove built-in tool seeding from startup - Remove is_builtin from API schemas and frontend types - Update tool-types spec to reflect removal of built-in concept - Add Alembic migration for column removal - Update tests to work without built-in distinction
134 lines
4.5 KiB
Python
134 lines
4.5 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from sqlalchemy import text
|
|
|
|
from src.api.auth import router as auth_router
|
|
from src.api.dashboard import router as dashboard_router
|
|
from src.api.git_repositories import router as git_repositories_router
|
|
from src.api.health import router as health_router
|
|
from src.api.projects import router as projects_router
|
|
from src.api.ssh_keys import router as ssh_keys_router
|
|
from src.api.terminal import router as terminal_router
|
|
from src.api.instance_proxy import router as instance_proxy_router
|
|
from src.api.config_folders import router as config_folders_router
|
|
from src.api.tool_configs import router as tool_configs_router
|
|
from src.api.tool_instances import router as tool_instances_router
|
|
from src.api.tool_instances import sessions_router
|
|
from src.api.tool_types import router as tool_types_router
|
|
from src.api.user_config import router as user_config_router
|
|
from src.api.users import router as users_router
|
|
from src.config import Settings
|
|
from src.database import init_database
|
|
from src.logging_config import (
|
|
ExceptionLoggingMiddleware,
|
|
RequestLoggingMiddleware,
|
|
configure_logging,
|
|
)
|
|
|
|
# Configure logging early
|
|
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
|
configure_logging(level=getattr(logging, log_level, logging.INFO))
|
|
|
|
logger = logging.getLogger(__name__)
|
|
settings = Settings()
|
|
app = FastAPI(title="Headquarter API")
|
|
|
|
# Configure CORS - must be before other middleware
|
|
# Build allowed origins list including web and api domains
|
|
cors_origins = [settings.web_base_url]
|
|
if settings.api_base_url != settings.web_base_url:
|
|
cors_origins.append(settings.api_base_url)
|
|
logger.info("CORS configured with origins: %s", cors_origins)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.add_middleware(RequestLoggingMiddleware)
|
|
app.add_middleware(ExceptionLoggingMiddleware)
|
|
|
|
|
|
def _sanitize_validation_errors(errors):
|
|
"""Convert validation errors to JSON-safe format."""
|
|
sanitized = []
|
|
for error in errors:
|
|
safe_error = {
|
|
"type": error.get("type"),
|
|
"loc": error.get("loc"),
|
|
"msg": error.get("msg"),
|
|
"input": str(error.get("input")) if error.get("input") is not None else None,
|
|
}
|
|
# Convert ctx to safe format
|
|
ctx = error.get("ctx")
|
|
if ctx:
|
|
safe_ctx = {}
|
|
for key, value in ctx.items():
|
|
if isinstance(value, Exception):
|
|
safe_ctx[key] = str(value)
|
|
elif isinstance(value, (str, int, float, bool, type(None))):
|
|
safe_ctx[key] = value
|
|
else:
|
|
safe_ctx[key] = str(value)
|
|
safe_error["ctx"] = safe_ctx
|
|
sanitized.append(safe_error)
|
|
return sanitized
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
"""Log validation errors and return detailed response."""
|
|
errors = exc.errors()
|
|
logger.warning(
|
|
"Validation error for %s %s: %s",
|
|
request.method,
|
|
request.url.path,
|
|
errors,
|
|
)
|
|
safe_errors = _sanitize_validation_errors(errors)
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content={"detail": safe_errors},
|
|
)
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def on_startup():
|
|
logger.info("Starting up Headquarter API...")
|
|
|
|
# Initialize database (run migrations)
|
|
db_ready = await init_database()
|
|
if not db_ready:
|
|
logger.error("Database initialization failed. Shutting down.")
|
|
import sys
|
|
sys.exit(1)
|
|
|
|
logger.info("Startup complete.")
|
|
|
|
app.include_router(health_router)
|
|
app.include_router(auth_router)
|
|
app.include_router(dashboard_router)
|
|
app.include_router(projects_router)
|
|
app.include_router(users_router)
|
|
app.include_router(ssh_keys_router)
|
|
app.include_router(git_repositories_router)
|
|
app.include_router(user_config_router)
|
|
app.include_router(tool_types_router)
|
|
app.include_router(config_folders_router)
|
|
app.include_router(tool_instances_router)
|
|
app.include_router(tool_configs_router)
|
|
app.include_router(sessions_router)
|
|
app.include_router(instance_proxy_router)
|
|
app.include_router(terminal_router)
|
|
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|