"""SmartUp FastAPI application entry point.""" import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, HTTPException 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, browser_sessions, websites from app.services.browser_session_service import browser_sessions as browser_session_service 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 await browser_session_service.shutdown() 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.include_router(browser_sessions.router) app.include_router(websites.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.api_route("/favicon.svg", methods=["GET", "HEAD"]) def serve_favicon(): favicon = STATIC_DIR / "favicon.svg" if not favicon.exists(): raise HTTPException(status_code=404, detail="favicon not found") return FileResponse(str(favicon), media_type="image/svg+xml") @app.get("/{full_path:path}") def serve_spa(full_path: str): index = STATIC_DIR / "index.html" return FileResponse(str(index))