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>
This commit is contained in:
183
backend/app/main.py
Normal file
183
backend/app/main.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user