1eabd7183c
Starlette Mount 가 FastAPI(root_path=...) 의 값을 mount 경로에 흡수해버려서
@app.get 라우트(/health/db)는 그대로지만 mount(/static)는 /orderlist/static
으로 등록되는 비대칭이 발생했다. NPM 이 /orderlist prefix 를 잘라서 백엔드로
보내면 /health/db 는 200 이지만 /static/css/style.css 는 404 가 났다.
해결: FastAPI 생성자에서 root_path 인자 제거. 대신 Dockerfile 의 uvicorn 명령에
--root-path "${APP_ROOT_PATH:-}" 를 추가해 ASGI scope 레벨에서만 root_path 가
세팅되도록 한다. 이러면 모든 라우트/마운트가 prefix 없이 등록되고 URL 생성
시에만 prefix 가 붙는다.
APP_ROOT_PATH 환경변수와 main.py 의 HTML 경로 치환 로직은 그대로 유지.
86 lines
4.0 KiB
Python
86 lines
4.0 KiB
Python
from fastapi import FastAPI, Depends, Request
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
from starlette.middleware.sessions import SessionMiddleware
|
|
import logging
|
|
import os
|
|
|
|
from app.database import engine, Base, check_db_connection, log_db_startup_status
|
|
from app.database_maintenance import ensure_search_optimizations
|
|
from app.routers import orders, settings
|
|
from app.auth import router as auth_router
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
APP_ROOT_PATH = os.getenv("APP_ROOT_PATH", "").rstrip("/")
|
|
|
|
# Verify DB connectivity at startup (log only — do not crash so the
|
|
# container can stay up and report status via /health/db).
|
|
log_db_startup_status()
|
|
|
|
# Create all DB tables
|
|
Base.metadata.create_all(bind=engine)
|
|
ensure_search_optimizations(engine)
|
|
|
|
# root_path 는 FastAPI 생성자에 넘기지 않는다.
|
|
# Starlette 의 Mount 가 root_path 를 흡수해 mount 경로에 prefix 가 붙어버려서
|
|
# @app.get 라우트와 mount 가 비대칭으로 등록되는 문제 때문이다.
|
|
# (예: /health/db 는 그대로지만 /static/* 는 /orderlist/static/* 로 등록됨)
|
|
#
|
|
# 대신 uvicorn --root-path 로 ASGI scope.root_path 만 세팅한다.
|
|
# 그러면 모든 라우트/마운트가 prefix 없이 등록되고, URL 생성 시에만 prefix 가 붙는다.
|
|
# Dockerfile 의 CMD 가 ${APP_ROOT_PATH} 를 --root-path 로 넘긴다.
|
|
app = FastAPI(title="Order Management System")
|
|
|
|
|
|
@app.get("/health/db")
|
|
async def health_db():
|
|
ok, msg = check_db_connection()
|
|
status_code = 200 if ok else 503
|
|
return JSONResponse({"ok": ok, "detail": msg}, status_code=status_code)
|
|
|
|
# ---- Session middleware ----
|
|
# dbx-main 과 SSO 하려면 SESSION_SECRET_KEY 와 SESSION_COOKIE_NAME 을
|
|
# 두 앱이 동일하게 맞춰야 한다. 그래야 dbx-main 이 발급한 세션 쿠키를
|
|
# OMS 가 그대로 읽을 수 있다.
|
|
_session_secret = os.getenv("SESSION_SECRET_KEY", "").strip()
|
|
if not _session_secret:
|
|
raise RuntimeError(
|
|
"SESSION_SECRET_KEY 환경변수가 설정되지 않았습니다. "
|
|
"openssl rand -hex 32 로 새 값을 만들어 .env 에 넣고 컨테이너를 재기동하세요. "
|
|
"dbx-main 과 SSO 하려면 양쪽 .env 에 같은 값이어야 합니다."
|
|
)
|
|
app.add_middleware(
|
|
SessionMiddleware,
|
|
secret_key=_session_secret,
|
|
session_cookie=os.getenv("SESSION_COOKIE_NAME", "session"),
|
|
same_site=os.getenv("SESSION_SAME_SITE", "lax"),
|
|
https_only=os.getenv("SESSION_HTTPS_ONLY", "0") == "1",
|
|
max_age=int(os.getenv("SESSION_MAX_AGE", "28800")), # 8h — dbx-main 과 통일
|
|
)
|
|
|
|
# Include routers
|
|
app.include_router(auth_router)
|
|
app.include_router(orders.router, prefix="/api/orders", tags=["orders"])
|
|
app.include_router(settings.router, prefix="/api/settings", tags=["settings"])
|
|
|
|
# Mount static files for frontend
|
|
os.makedirs("static", exist_ok=True)
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def read_root():
|
|
# Return the main index.html file
|
|
if os.path.exists("static/index.html"):
|
|
with open("static/index.html", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
content = content.replace("__APP_BASE_PATH__", APP_ROOT_PATH)
|
|
content = content.replace('src="/static/', f'src="{APP_ROOT_PATH}/static/' if APP_ROOT_PATH else 'src="/static/')
|
|
content = content.replace('href="/static/', f'href="{APP_ROOT_PATH}/static/' if APP_ROOT_PATH else 'href="/static/')
|
|
content = content.replace("window.location.href='/login", f"window.location.href='{APP_ROOT_PATH}/login")
|
|
content = content.replace('window.location.href="/login', f'window.location.href="{APP_ROOT_PATH}/login')
|
|
content = content.replace("window.location.href='/logout", f"window.location.href='{APP_ROOT_PATH}/logout")
|
|
content = content.replace('window.location.href="/logout', f'window.location.href="{APP_ROOT_PATH}/logout')
|
|
return HTMLResponse(content=content)
|
|
return HTMLResponse(content="<h1>Welcome. static/index.html is missing.</h1>")
|