Files
GitMa/backend/app/main.py
panw 44921c5646 feat: complete Git Repo Manager MVP implementation
Backend (Phase 1-6):
- Pydantic schemas for request/response validation
- Service layer (SSH Key, Server, Repo, Sync)
- API routes with authentication
- FastAPI main application with lifespan management
- ORM models (SshKey, Server, Repo, SyncLog)

Frontend (Phase 7):
- Vue 3 + Element Plus + Pinia + Vue Router
- API client with Axios and interceptors
- State management stores
- All page components (Dashboard, Servers, Repos, SyncLogs, SshKeys, Settings)

Deployment (Phase 8):
- README with quick start guide
- Startup script (start.sh)

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

184 lines
4.9 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
"""
from contextlib import asynccontextmanager
from pathlib import Path
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
)