Files
2026-07-13 13:08:41 +08:00

108 lines
3.2 KiB
Python

import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request
from starlette.responses import FileResponse
from api.lifespan import app_lifespan
from api.middlewares import SessionAuthMiddleware, UserConfigEnvUpdateMiddleware
from api.v1.async_tasks.router import API_V1_ASYNC_TASKS_ROUTER
from api.v1.auth.router import API_V1_AUTH_ROUTER
from api.v1.mock.router import API_V1_MOCK_ROUTER
from api.v1.ppt.router import API_V1_PPT_ROUTER
from api.v1.webhook.router import API_V1_WEBHOOK_ROUTER
from utils.get_env import (
get_app_data_directory_env,
get_sentry_dsn_env,
get_sentry_send_default_pii_env,
get_sentry_traces_sample_rate_env,
)
from utils.mime_types import init_sandbox_safe_mimetypes
from utils.path_helpers import get_resource_path
init_sandbox_safe_mimetypes()
def _maybe_init_sentry() -> None:
sentry_dsn = get_sentry_dsn_env()
if not sentry_dsn:
return
try:
import sentry_sdk
except Exception:
# Sentry SDK is optional in some runtime targets.
return
traces_sample_rate = get_sentry_traces_sample_rate_env()
send_default_pii = get_sentry_send_default_pii_env()
try:
parsed_sample_rate = (
float(traces_sample_rate) if traces_sample_rate is not None else 1.0
)
except ValueError:
parsed_sample_rate = 1.0
parsed_send_default_pii = (
send_default_pii.lower() == "true" if send_default_pii is not None else True
)
sentry_sdk.init(
dsn=sentry_dsn,
send_default_pii=parsed_send_default_pii,
traces_sample_rate=parsed_sample_rate,
)
_maybe_init_sentry()
app = FastAPI(lifespan=app_lifespan)
# Routers
app.include_router(API_V1_PPT_ROUTER)
app.include_router(API_V1_WEBHOOK_ROUTER)
app.include_router(API_V1_MOCK_ROUTER)
app.include_router(API_V1_AUTH_ROUTER)
app.include_router(API_V1_ASYNC_TASKS_ROUTER)
# Mount app_data and static assets (direct FastAPI access; nginx also serves /static in Docker).
app_data_dir = get_app_data_directory_env()
if app_data_dir:
os.makedirs(app_data_dir, exist_ok=True)
app.mount("/app_data", StaticFiles(directory=app_data_dir), name="app_data")
static_dir = get_resource_path("static")
if os.path.isdir(static_dir):
app.mount("/static", StaticFiles(directory=static_dir), name="static")
# Middlewares
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(UserConfigEnvUpdateMiddleware)
app.add_middleware(SessionAuthMiddleware)
@app.middleware("http")
async def static_icon_fallback_middleware(request: Request, call_next):
"""Serve placeholder when icon paths are missing (e.g. renamed Phosphor icons)."""
response = await call_next(request)
if response.status_code != 404:
return response
path = request.url.path
if not path.startswith("/static/icons/"):
return response
placeholder = get_resource_path("static/icons/placeholder.svg")
if not os.path.isfile(placeholder):
return response
return FileResponse(placeholder, media_type="image/svg+xml")