"""SmartUp FastAPI application entry point.""" import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from app.config import get_settings from app.database import init_db from app.models.admin_user import AdminUser from app.database import SessionLocal from app.utils.auth import hash_password from app.services.scheduler import start_scheduler, stop_scheduler from app.routers import auth, upstreams, webhooks, logs, custom_pages logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s") logger = logging.getLogger(__name__) def _init_admin() -> None: settings = get_settings() if not settings.admin_password: logger.warning("ADMIN_PASSWORD not set, skip admin init") return db = SessionLocal() try: exists = db.query(AdminUser).filter(AdminUser.email == settings.admin_email).first() if not exists: user = AdminUser( email=settings.admin_email, password_hash=hash_password(settings.admin_password), ) db.add(user) db.commit() logger.info("admin user created: %s", settings.admin_email) else: logger.info("admin user already exists: %s", settings.admin_email) finally: db.close() @asynccontextmanager async def lifespan(app: FastAPI): init_db() _init_admin() start_scheduler() yield stop_scheduler() app = FastAPI( title="SmartUp", description="API 上游管理与 Webhook 通知系统", version="1.0.0", lifespan=lifespan, docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # API routers app.include_router(auth.router) app.include_router(upstreams.router) app.include_router(webhooks.router) app.include_router(logs.router) app.include_router(custom_pages.router) @app.get("/healthz") def health(): return {"status": "ok"} # Serve frontend static files STATIC_DIR = Path(__file__).parent.parent / "static" if STATIC_DIR.exists(): app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets")), name="assets") @app.get("/{full_path:path}") def serve_spa(full_path: str): index = STATIC_DIR / "index.html" return FileResponse(str(index))