Files
GitMa/backend/app/main.py
panw c625425971 fix: Windows compatibility and startup scripts
- Add explicit .env loading in config.py for Windows compatibility
- Add backend directory to sys.path in main.py to fix module imports
- Add start.bat and start-full.bat for Windows startup
- Add frontend/package-lock.json for dependency tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 19:23:23 +08:00

191 lines
5.1 KiB
Python

"""
FastAPI main application.
This module creates and configures the FastAPI application with:
- All API routers registered
- Lifespan events for database initialization
- Static file serving for the frontend
- CORS middleware
- Exception handlers
"""
import sys
from pathlib import Path
# Add backend directory to Python path for imports to work
backend_dir = Path(__file__).parent.parent
if str(backend_dir) not in sys.path:
sys.path.insert(0, str(backend_dir))
from contextlib import asynccontextmanager
from typing import Callable
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.exc import SQLAlchemyError
from app.config import get_settings
from app.database import init_db, get_engine
from app.models import Base # noqa: F401 - Import to ensure models are registered
# Import API routers
from app.api.ssh_keys import router as ssh_keys_router
from app.api.servers import router as servers_router
from app.api.status import router as status_router
@asynccontextmanager
async def lifespan(app: FastAPI): # noqa: ARG001 - Unused app parameter
"""
Lifespan context manager for FastAPI application.
Handles startup and shutdown events:
- Startup: Initialize database and create tables
- Shutdown: Close database connections
Yields:
None
"""
# Startup
settings = get_settings()
# Initialize database
init_db(settings.db_path)
# Create all tables
engine = get_engine()
if engine is not None:
Base.metadata.create_all(engine)
# Ensure required directories exist
settings.data_dir.mkdir(parents=True, exist_ok=True)
settings.ssh_keys_dir.mkdir(parents=True, exist_ok=True)
settings.repos_dir.mkdir(parents=True, exist_ok=True)
yield
# Shutdown
# Close database connections
if engine is not None:
engine.dispose()
def create_app(lifespan_handler: Callable = lifespan) -> FastAPI:
"""
Create and configure the FastAPI application.
Args:
lifespan_handler: Lifespan context manager (for testing)
Returns:
Configured FastAPI application instance
"""
settings = get_settings()
app = FastAPI(
title="Git Manager API",
description="API for managing Gitea server mirrors and SSH keys",
version="1.0.0",
lifespan=lifespan_handler,
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json"
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # In production, specify exact origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Register exception handlers
register_exception_handlers(app)
# Register API routers
app.include_router(ssh_keys_router)
app.include_router(servers_router)
app.include_router(status_router)
# Mount static files for frontend
# Check if frontend build exists
frontend_path = Path(__file__).parent.parent.parent / "frontend" / "dist"
if frontend_path.exists():
app.mount("/", StaticFiles(directory=str(frontend_path), html=True), name="frontend")
return app
def register_exception_handlers(app: FastAPI) -> None:
"""
Register global exception handlers for the application.
Args:
app: FastAPI application instance
"""
@app.exception_handler(SQLAlchemyError)
async def sqlalchemy_error_handler(
request: Request, # noqa: ARG001 - Unused request parameter
exc: SQLAlchemyError
):
"""Handle SQLAlchemy database errors."""
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"code": 500,
"message": "Database error occurred",
"data": {"detail": str(exc)}
}
)
@app.exception_handler(ValueError)
async def value_error_handler(
request: Request, # noqa: ARG001 - Unused request parameter
exc: ValueError
):
"""Handle ValueError exceptions."""
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={
"code": 400,
"message": str(exc),
"data": None
}
)
@app.exception_handler(Exception)
async def general_exception_handler(
request: Request, # noqa: ARG001 - Unused request parameter
exc: Exception
):
"""Handle all other exceptions."""
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"code": 500,
"message": "Internal server error",
"data": {"detail": str(exc)}
}
)
# Create the application instance
app = create_app()
if __name__ == "__main__":
import uvicorn
settings = get_settings()
uvicorn.run(
"app.main:app",
host=settings.host,
port=settings.port,
reload=True
)