81 lines
3.1 KiB
Python
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.operations import run_operations_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_operations_scheduler(container.operations, 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
|