| """ |
| Main FastAPI application entry point |
| """ |
|
|
| from fastapi import FastAPI, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.staticfiles import StaticFiles |
| from fastapi.responses import HTMLResponse, FileResponse |
| import uvicorn |
| import asyncio |
| import logging |
| import os |
| import sys |
| from .api.openai_compat import router as openai_router |
| from .api.landppt_api import router as landppt_router |
| from .api.database_api import router as database_router |
| from .api.global_master_template_api import router as template_api_router |
| from .api.config_api import router as config_router |
| from .api.image_api import router as image_router |
|
|
| from .web import router as web_router |
| from .web.admin_routes import router as admin_router |
| from .web.community_routes import router as community_router |
| from .web.credits_routes import router as credits_router |
| from .auth import auth_router, create_auth_middleware |
| from .database.startup_initialization import run_startup_initialization |
| from .core.config import app_config |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| |
| logging.getLogger('sqlalchemy').setLevel(logging.WARNING) |
| logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) |
| logging.getLogger('sqlalchemy.engine.Engine').setLevel(logging.WARNING) |
| logging.getLogger('sqlalchemy.pool').setLevel(logging.WARNING) |
| logging.getLogger('sqlalchemy.dialects').setLevel(logging.WARNING) |
|
|
| APP_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) |
| RESOURCE_ROOT = getattr(sys, '_MEIPASS', APP_ROOT) |
| PROJECT_ROOT = os.path.abspath(os.path.join(APP_ROOT, os.pardir)) |
|
|
|
|
| def _resource_path(*parts: str) -> str: |
| return os.path.join(RESOURCE_ROOT, *parts) |
|
|
|
|
| def _project_path(*parts: str) -> str: |
| return os.path.join(PROJECT_ROOT, *parts) |
|
|
|
|
| |
| app = FastAPI( |
| title="LandPPT API", |
| description="AI-powered PPT generation platform with OpenAI-compatible API", |
| version="0.1.0", |
| docs_url="/docs" if app_config.enable_api_docs else None, |
| redoc_url="/redoc" if app_config.enable_api_docs else None, |
| openapi_url="/openapi.json" if app_config.enable_api_docs else None, |
| ) |
|
|
|
|
| @app.on_event("startup") |
| async def startup_event(): |
| """Initialize database on startup""" |
| try: |
| await run_startup_initialization() |
|
|
| except Exception as e: |
| logger.error(f"Failed to initialize application: {e}") |
| raise |
|
|
|
|
| @app.on_event("shutdown") |
| async def shutdown_event(): |
| """Clean up database connections on shutdown""" |
| try: |
| logger.info("Shutting down application...") |
| |
| try: |
| from .services.cache_service import close_cache_service |
| await close_cache_service() |
| except Exception: |
| pass |
| logger.info("Application shutdown complete") |
| except Exception as e: |
| logger.error(f"Error during shutdown: {e}") |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| auth_middleware = create_auth_middleware() |
| app.middleware("http")(auth_middleware) |
|
|
| |
| app.include_router(auth_router, prefix="", tags=["Authentication"]) |
| app.include_router(config_router, prefix="", tags=["Configuration Management"]) |
| app.include_router(image_router, prefix="", tags=["Image Service"]) |
|
|
| |
| app.include_router(web_router, prefix="", tags=["Web Interface"]) |
| app.include_router(admin_router, tags=["Admin Management"]) |
| app.include_router(community_router, tags=["Community Pages"]) |
| app.include_router(credits_router, tags=["Credits System"]) |
| app.include_router(openai_router, prefix="/v1", tags=["OpenAI Compatible"]) |
| app.include_router(landppt_router, prefix="/api", tags=["LandPPT API"]) |
| app.include_router(template_api_router, tags=["Global Master Templates"]) |
| app.include_router(database_router, tags=["Database Management"]) |
|
|
|
|
| |
| static_dir = _resource_path("src", "landppt", "web", "static") |
| if not os.path.exists(static_dir): |
| static_dir = os.path.join(os.path.dirname(__file__), "web", "static") |
| app.mount("/static", StaticFiles(directory=static_dir), name="static") |
|
|
| |
| |
| |
| import re |
| _FONTSOURCE_RE = re.compile( |
| r'^(?P<family>[a-z0-9-]+?)-(?P<subset>[a-z]+)-(?P<weight>\d+)-(?P<style>[a-z]+)-[A-Za-z0-9_-]+\.woff2$' |
| ) |
|
|
| @app.get("/assets/{filename:path}") |
| async def serve_font_asset(filename: str): |
| """Redirect fontsource-style font requests to jsDelivr CDN""" |
| from fastapi.responses import RedirectResponse |
| m = _FONTSOURCE_RE.match(filename) |
| if m: |
| family = m.group('family') |
| subset = m.group('subset') |
| weight = m.group('weight') |
| style = m.group('style') |
| cdn_url = f"https://cdn.jsdelivr.net/fontsource/fonts/{family}@latest/{subset}-{weight}-{style}.woff2" |
| return RedirectResponse(url=cdn_url, status_code=301) |
| raise HTTPException(status_code=404, detail="Asset not found") |
|
|
| |
| temp_dir_candidates = [ |
| os.path.join(os.getcwd(), "temp"), |
| _project_path("temp"), |
| ] |
| temp_dir = next((path for path in temp_dir_candidates if os.path.exists(path)), None) |
| if temp_dir: |
| app.mount("/temp", StaticFiles(directory=temp_dir), name="temp") |
| logger.info(f"Mounted temp directory: {temp_dir}") |
| else: |
| logger.warning("Temp directory not found") |
|
|
| @app.get("/", response_class=HTMLResponse) |
| async def root(): |
| """Root endpoint - redirect to dashboard""" |
| from fastapi.responses import RedirectResponse |
| return RedirectResponse(url="/dashboard", status_code=302) |
|
|
| @app.get("/favicon.ico") |
| async def favicon(): |
| """Serve favicon""" |
| favicon_candidates = [ |
| os.path.join(static_dir, "images", "landppt-logo.ico"), |
| os.path.join(static_dir, "images", "favicon.ico"), |
| os.path.join(static_dir, "images", "favicon.svg"), |
| ] |
| for favicon_path in favicon_candidates: |
| if os.path.exists(favicon_path): |
| media_type = "image/svg+xml" if favicon_path.lower().endswith(".svg") else "image/x-icon" |
| return FileResponse(favicon_path, media_type=media_type) |
| raise HTTPException(status_code=404, detail="Favicon not found") |
|
|
| @app.get("/landppt-api-config-guide", response_class=HTMLResponse) |
| async def landppt_api_config_guide(): |
| """Serve the public LandPPT API configuration guide.""" |
| guide_candidates = [ |
| _resource_path("API 配置", "LANDPPT API 配置说明书.html"), |
| _project_path("API 配置", "LANDPPT API 配置说明书.html"), |
| os.path.join(os.getcwd(), "API 配置", "LANDPPT API 配置说明书.html"), |
| ] |
| for guide_path in guide_candidates: |
| if os.path.exists(guide_path): |
| return FileResponse(guide_path, media_type="text/html; charset=utf-8") |
| raise HTTPException(status_code=404, detail="LANDPPT API 配置说明书.html not found") |
|
|
| @app.get("/landppt-api-config-guide-assets/{filename:path}") |
| async def landppt_api_config_guide_assets(filename: str): |
| """Serve images referenced by the public LandPPT API configuration guide.""" |
| safe_name = os.path.basename(filename) |
| asset_candidates = [ |
| _resource_path("API 配置", safe_name), |
| _project_path("API 配置", safe_name), |
| os.path.join(os.getcwd(), "API 配置", safe_name), |
| ] |
| asset_path = next((path for path in asset_candidates if os.path.exists(path) and os.path.isfile(path)), "") |
| if not asset_path: |
| raise HTTPException(status_code=404, detail="Guide asset not found") |
|
|
| ext = os.path.splitext(safe_name)[1].lower() |
| media_types = { |
| ".png": "image/png", |
| ".jpg": "image/jpeg", |
| ".jpeg": "image/jpeg", |
| ".webp": "image/webp", |
| ".gif": "image/gif", |
| ".svg": "image/svg+xml", |
| } |
| return FileResponse(asset_path, media_type=media_types.get(ext, "application/octet-stream")) |
|
|
| @app.get("/health") |
| async def health_check(): |
| """Health check endpoint""" |
| return {"status": "healthy", "service": "LandPPT API"} |
|
|
| if __name__ == "__main__": |
| uvicorn.run( |
| "src.landppt.main:app", |
| host="0.0.0.0", |
| port=8000, |
| reload=True, |
| log_level="info" |
| ) |
|
|