diff --git a/.env.example b/.env.example index f16c9eb..e77f43f 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ HOST=0.0.0.0 PORT=8000 LOG_LEVEL=info RELOAD=false +# 리버스 프록시 sub-path 배포 시. 예: /plagiarism (Apache가 /plagiarism → 컨테이너 매핑할 때) +ROOT_PATH= ENGINE_VERSION=o2o-plagiarism-2.1.0-kosimcse REFERENCE_CORPUS_DIR=./data/reference diff --git a/app/core/config.py b/app/core/config.py index 27b044b..68c1dd6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -12,6 +12,7 @@ class Settings(BaseSettings): port: int = 8000 log_level: str = "info" # debug / info / warning / error reload: bool = False # 개발용 자동 재시작 + root_path: str = "" # 리버스 프록시 sub-path (예: /plagiarism) engine_version: str = "o2o-plagiarism-2.0.0-pdf-v1.2" reference_corpus_dir: str = "./data/reference" diff --git a/app/main.py b/app/main.py index 1e399fa..31ee423 100644 --- a/app/main.py +++ b/app/main.py @@ -40,6 +40,8 @@ def rebuild_detector(app: FastAPI) -> int: return app.state.detector.corpus_size +_settings = get_settings() + app = FastAPI( title="O2O 저작권 침해 여부 탐지 API", description=( @@ -48,6 +50,7 @@ app = FastAPI( ), version="1.0.0", lifespan=lifespan, + root_path=_settings.root_path, ) app.include_router(api_router) @@ -83,6 +86,9 @@ def main() -> None: port=settings.port, log_level=settings.log_level, reload=settings.reload, + root_path=settings.root_path, + forwarded_allow_ips="*", # 리버스 프록시(Apache) 헤더 신뢰 + proxy_headers=True, ) diff --git a/app/static/index.html b/app/static/index.html index 08457a6..1f4fb3e 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -351,8 +351,8 @@ @@ -445,7 +445,7 @@ async function runDetect() { document.getElementById("result-body").style.display = "none"; try { - const resp = await fetch("/v1/plagiarism/detect", { + const resp = await fetch("v1/plagiarism/detect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), @@ -613,7 +613,7 @@ async function loadCorpus() { const tbody = document.getElementById("corpus-tbody"); tbody.innerHTML = '로딩 중…'; try { - const resp = await fetch("/v1/corpus"); + const resp = await fetch("v1/corpus"); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); document.getElementById("corpus-count").textContent = data.total; @@ -664,9 +664,9 @@ async function uploadCorpus() { fd.append("title", title); if (docId) fd.append("doc_id", docId); fd.append("file", file); - resp = await fetch("/v1/corpus/file", { method: "POST", body: fd }); + resp = await fetch("v1/corpus/file", { method: "POST", body: fd }); } else { - resp = await fetch("/v1/corpus", { + resp = await fetch("v1/corpus", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ doc_id: docId || null, title, text }), @@ -695,7 +695,7 @@ async function uploadCorpus() { async function deleteDoc(docId) { if (!confirm(`'${docId}' 문서를 삭제하시겠습니까? 인덱스가 재빌드됩니다.`)) return; try { - const resp = await fetch(`/v1/corpus/${encodeURIComponent(docId)}`, { + const resp = await fetch(`v1/corpus/${encodeURIComponent(docId)}`, { method: "DELETE", }); if (!resp.ok && resp.status !== 204) { @@ -728,7 +728,7 @@ function escapeJs(s) { // 헬스 체크 + 코퍼스 정보 표시 async function checkHealth() { try { - const resp = await fetch("/v1/health"); + const resp = await fetch("v1/health"); if (!resp.ok) throw new Error("not ok"); const data = await resp.json(); const autobio = data.autobiography_mode ? ' · 자서전모드' : '';