Files
xiaobaifupan/next/backend/bootstrap/application.py
T

81 lines
3.1 KiB
Python

import asyncio
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from backend.bootstrap.container import build_container
from backend.bootstrap.logging import configure_logging
from backend.bootstrap.settings import Settings
from backend.database.migrations import MIGRATIONS, MigrationRunner
from backend.http.errors import install_error_handlers
from backend.http.request_context import install_request_context
from backend.http.router import api_router
from backend.http.security import install_security_headers
from backend.jobs.screener import run_screener_scheduler
logger = logging.getLogger(__name__)
def create_application(settings: Settings | None = None) -> FastAPI:
runtime = settings or Settings.from_environment()
container = build_container(runtime)
@asynccontextmanager
async def lifespan(_application: FastAPI):
runtime.data_directory.mkdir(parents=True, exist_ok=True)
configure_logging(runtime.log_level, runtime.log_file)
MigrationRunner(container.database).upgrade(MIGRATIONS)
database_status = container.database_status.get()
logger.info(
"Application started",
extra={
"event": "application.started",
"context": {
"environment": runtime.environment,
"schema_version": database_status.schema_version,
},
},
)
stop = asyncio.Event()
task = None
if runtime.environment != "test":
task = asyncio.create_task(run_screener_scheduler(container.screener, stop))
try:
yield
finally:
if task:
stop.set()
await task
application = FastAPI(
title="小白复盘",
version="0.1.0",
docs_url="/api/docs" if runtime.debug else None,
redoc_url=None,
lifespan=lifespan,
)
application.state.settings = runtime
application.state.container = container
install_request_context(application)
install_security_headers(application)
install_error_handlers(application)
application.include_router(api_router, prefix="/api")
if runtime.frontend_dist_directory.joinpath("index.html").is_file():
frontend_root = runtime.frontend_dist_directory.resolve()
@application.get("/{requested_path:path}", include_in_schema=False)
def frontend(requested_path: str) -> FileResponse:
if requested_path == "api" or requested_path.startswith("api/"):
raise HTTPException(status_code=404)
candidate = frontend_root.joinpath(requested_path).resolve()
if candidate.is_relative_to(frontend_root) and candidate.is_file():
response = FileResponse(candidate)
if candidate.parent.name == "assets":
response.headers["Cache-Control"] = "public,max-age=31536000,immutable"
return response
return FileResponse(frontend_root / "index.html", headers={"Cache-Control": "no-cache"})
return application