Update app.py
Browse files
app.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
from fastapi import FastAPI, BackgroundTasks, UploadFile, File, Form
|
| 2 |
-
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
-
import pathlib, os, uvicorn, base64, json, shutil
|
| 5 |
-
from typing import Dict, List, Any
|
| 6 |
import asyncio
|
| 7 |
import logging
|
| 8 |
import threading
|
|
@@ -31,6 +31,12 @@ CACHE_DIR = BASE / "cache"
|
|
| 31 |
if not CACHE_DIR.exists():
|
| 32 |
CACHE_DIR.mkdir(parents=True)
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
# ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ
|
| 35 |
ADMIN_PASSWORD = os.getenv("PASSWORD", "admin") # ํ๊ฒฝ ๋ณ์์์ ๊ฐ์ ธ์ค๊ธฐ, ๊ธฐ๋ณธ๊ฐ์ ํ
์คํธ์ฉ
|
| 36 |
|
|
@@ -38,6 +44,46 @@ ADMIN_PASSWORD = os.getenv("PASSWORD", "admin") # ํ๊ฒฝ ๋ณ์์์ ๊ฐ์ ธ์ค
|
|
| 38 |
pdf_cache: Dict[str, Dict[str, Any]] = {}
|
| 39 |
# ์บ์ฑ ๋ฝ
|
| 40 |
cache_locks = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# PDF ํ์ผ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ (๋ฉ์ธ ๋๋ ํ ๋ฆฌ์ฉ)
|
| 43 |
def get_pdf_files():
|
|
@@ -53,7 +99,6 @@ def get_permanent_pdf_files():
|
|
| 53 |
pdf_files = [f for f in PERMANENT_PDF_DIR.glob("*.pdf")]
|
| 54 |
return pdf_files
|
| 55 |
|
| 56 |
-
# PDF ์ธ๋ค์ผ ์์ฑ ๋ฐ ํ๋ก์ ํธ ๋ฐ์ดํฐ ์ค๋น
|
| 57 |
# PDF ์ธ๋ค์ผ ์์ฑ ๋ฐ ํ๋ก์ ํธ ๋ฐ์ดํฐ ์ค๋น
|
| 58 |
def generate_pdf_projects():
|
| 59 |
projects_data = []
|
|
@@ -75,15 +120,28 @@ def generate_pdf_projects():
|
|
| 75 |
|
| 76 |
# ์ค๋ณต ์ ๊ฑฐ๋ ํ์ผ๋ค๋ก ํ๋ก์ ํธ ๋ฐ์ดํฐ ์์ฑ
|
| 77 |
for pdf_file in unique_files.values():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
projects_data.append({
|
| 79 |
"path": str(pdf_file),
|
| 80 |
"name": pdf_file.stem,
|
|
|
|
| 81 |
"cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
|
| 82 |
})
|
| 83 |
|
| 84 |
return projects_data
|
| 85 |
|
| 86 |
-
|
| 87 |
# ์บ์ ํ์ผ ๊ฒฝ๋ก ์์ฑ
|
| 88 |
def get_cache_path(pdf_name: str):
|
| 89 |
return CACHE_DIR / f"{pdf_name}_cache.json"
|
|
@@ -242,10 +300,74 @@ async def cache_pdf(pdf_path: str):
|
|
| 242 |
pdf_cache[pdf_name]["status"] = "error"
|
| 243 |
pdf_cache[pdf_name]["error"] = str(e)
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
# ์์ ์ ๋ชจ๋ PDF ํ์ผ ์บ์ฑ
|
| 246 |
async def init_cache_all_pdfs():
|
| 247 |
logger.info("PDF ์บ์ฑ ์์
์์")
|
| 248 |
|
|
|
|
|
|
|
|
|
|
| 249 |
# ๋ฉ์ธ ๋ฐ ์๊ตฌ ๋๋ ํ ๋ฆฌ์์ PDF ํ์ผ ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ
|
| 250 |
pdf_files = get_pdf_files() + get_permanent_pdf_files()
|
| 251 |
|
|
@@ -253,6 +375,25 @@ async def init_cache_all_pdfs():
|
|
| 253 |
unique_pdf_paths = set(str(p) for p in pdf_files)
|
| 254 |
pdf_files = [pathlib.Path(p) for p in unique_pdf_paths]
|
| 255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
# ์ด๋ฏธ ์บ์๋ PDF ํ์ผ ๋ก๋ (๋น ๋ฅธ ์์์ ์ํด ๋จผ์ ์ํ)
|
| 257 |
for cache_file in CACHE_DIR.glob("*_cache.json"):
|
| 258 |
try:
|
|
@@ -275,6 +416,28 @@ async def init_cache_all_pdfs():
|
|
| 275 |
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์์ ํจ์
|
| 276 |
@app.on_event("startup")
|
| 277 |
async def startup_event():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
# ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์คํฌ๋ก ์บ์ฑ ์คํ
|
| 279 |
asyncio.create_task(init_cache_all_pdfs())
|
| 280 |
|
|
@@ -290,14 +453,43 @@ async def get_permanent_pdf_projects_api():
|
|
| 290 |
projects_data = []
|
| 291 |
|
| 292 |
for pdf_file in pdf_files:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
projects_data.append({
|
| 294 |
"path": str(pdf_file),
|
| 295 |
"name": pdf_file.stem,
|
|
|
|
| 296 |
"cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
|
| 297 |
})
|
| 298 |
|
| 299 |
return projects_data
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
# API ์๋ํฌ์ธํธ: PDF ์ธ๋ค์ผ ์ ๊ณต (์ต์ ํ)
|
| 302 |
@app.get("/api/pdf-thumbnail")
|
| 303 |
async def get_pdf_thumbnail(path: str):
|
|
@@ -422,7 +614,6 @@ async def get_pdf_content(path: str, background_tasks: BackgroundTasks):
|
|
| 422 |
logger.error(f"PDF ์ฝํ
์ธ ๋ก๋ ์ค๋ฅ: {str(e)}\n{error_details}")
|
| 423 |
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 424 |
|
| 425 |
-
|
| 426 |
# PDF ์
๋ก๋ ์๋ํฌ์ธํธ - ์๊ตฌ ์ ์ฅ์์ ์ ์ฅ ๋ฐ ๋ฉ์ธ ํ๋ฉด์ ์๋ ํ์
|
| 427 |
@app.post("/api/upload-pdf")
|
| 428 |
async def upload_pdf(file: UploadFile = File(...)):
|
|
@@ -446,11 +637,22 @@ async def upload_pdf(file: UploadFile = File(...)):
|
|
| 446 |
with open(PDF_DIR / file.filename, "wb") as buffer:
|
| 447 |
buffer.write(content)
|
| 448 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
# ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์บ์ฑ ์์
|
| 450 |
asyncio.create_task(cache_pdf(str(file_path)))
|
| 451 |
|
| 452 |
return JSONResponse(
|
| 453 |
-
content={
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
status_code=200
|
| 455 |
)
|
| 456 |
except Exception as e:
|
|
@@ -462,7 +664,6 @@ async def upload_pdf(file: UploadFile = File(...)):
|
|
| 462 |
status_code=500
|
| 463 |
)
|
| 464 |
|
| 465 |
-
|
| 466 |
# ๊ด๋ฆฌ์ ์ธ์ฆ ์๋ํฌ์ธํธ
|
| 467 |
@app.post("/api/admin-login")
|
| 468 |
async def admin_login(password: str = Form(...)):
|
|
@@ -491,6 +692,17 @@ async def delete_pdf(path: str):
|
|
| 491 |
if pdf_name in pdf_cache:
|
| 492 |
del pdf_cache[pdf_name]
|
| 493 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
return {"success": True}
|
| 495 |
except Exception as e:
|
| 496 |
logger.error(f"PDF ์ญ์ ์ค๋ฅ: {str(e)}")
|
|
@@ -528,13 +740,83 @@ async def unfeature_pdf(path: str):
|
|
| 528 |
logger.error(f"PDF ํ์ ํด์ ์ค๋ฅ: {str(e)}")
|
| 529 |
return {"success": False, "message": str(e)}
|
| 530 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
# HTML ํ์ผ ์ฝ๊ธฐ ํจ์
|
| 532 |
-
def get_html_content():
|
| 533 |
html_path = BASE / "flipbook_template.html"
|
|
|
|
| 534 |
if html_path.exists():
|
| 535 |
with open(html_path, "r", encoding="utf-8") as f:
|
| 536 |
-
|
| 537 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
# HTML ๋ฌธ์์ด (UI ์์ ๋ฒ์ )
|
| 540 |
HTML = """
|
|
@@ -1298,6 +1580,9 @@ HTML = """
|
|
| 1298 |
// ํ์ฌ ํ์ด์ง ๋ก๋ฉ ์ํ
|
| 1299 |
let currentLoadingPdfPath = null;
|
| 1300 |
let pageLoadingInterval = null;
|
|
|
|
|
|
|
|
|
|
| 1301 |
|
| 1302 |
/* ๐ ์ค๋์ค unlock โ ๋ด์ฅ Audio ์ ๊ฐ์ MP3 ๊ฒฝ๋ก ์ฌ์ฉ */
|
| 1303 |
['click','touchstart'].forEach(evt=>{
|
|
@@ -1371,7 +1656,7 @@ HTML = """
|
|
| 1371 |
await loadServerPDFs();
|
| 1372 |
|
| 1373 |
// ์ฑ๊ณต ๋ฉ์์ง
|
| 1374 |
-
showMessage("PDF๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์
๋ก๋๋์์ต๋๋ค!");
|
| 1375 |
} else {
|
| 1376 |
hideLoading();
|
| 1377 |
showError("์
๋ก๋ ์คํจ: " + (result.message || "์ ์ ์๋ ์ค๋ฅ"));
|
|
@@ -1382,38 +1667,52 @@ HTML = """
|
|
| 1382 |
showError("PDF ์
๋ก๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.");
|
| 1383 |
}
|
| 1384 |
}
|
| 1385 |
-
|
| 1386 |
-
|
| 1387 |
-
|
| 1388 |
-
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
|
| 1394 |
-
|
| 1395 |
-
|
| 1396 |
-
|
| 1397 |
-
|
| 1398 |
-
|
| 1399 |
-
|
| 1400 |
-
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
|
| 1413 |
/* โโ ํ๋ก์ ํธ ์ ์ฅ โโ */
|
| 1414 |
-
function save(pages, title, isCached = false){
|
| 1415 |
-
const id=projects.push(pages)-1;
|
| 1416 |
-
addCard(id, pages[0].thumb, title, isCached);
|
| 1417 |
}
|
| 1418 |
|
| 1419 |
/* โโ ์๋ฒ PDF ๋ก๋ ๋ฐ ์บ์ ์ํ ํ์ธ โโ */
|
|
@@ -1465,7 +1764,8 @@ HTML = """
|
|
| 1465 |
return {
|
| 1466 |
pages,
|
| 1467 |
name: project.name,
|
| 1468 |
-
isCached
|
|
|
|
| 1469 |
};
|
| 1470 |
}
|
| 1471 |
} catch (err) {
|
|
@@ -1480,7 +1780,7 @@ HTML = """
|
|
| 1480 |
|
| 1481 |
// ์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ธ์จ ๊ฒฐ๊ณผ๋ง ํ์
|
| 1482 |
results.filter(result => result !== null).forEach(result => {
|
| 1483 |
-
save(result.pages, result.name, result.isCached);
|
| 1484 |
});
|
| 1485 |
|
| 1486 |
hideLoading();
|
|
@@ -1565,11 +1865,120 @@ HTML = """
|
|
| 1565 |
}
|
| 1566 |
}
|
| 1567 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1568 |
/* โโ ์นด๋ โ FlipBook โโ */
|
| 1569 |
async function open(i) {
|
| 1570 |
toggle(false);
|
| 1571 |
const pages = projects[i];
|
| 1572 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1573 |
// ๊ธฐ์กด FlipBook ์ ๋ฆฌ
|
| 1574 |
if(fb) {
|
| 1575 |
fb.destroy();
|
|
@@ -1833,7 +2242,7 @@ HTML = """
|
|
| 1833 |
enableDownload: false,
|
| 1834 |
enablePrint: false,
|
| 1835 |
enableZoom: true,
|
| 1836 |
-
enableShare:
|
| 1837 |
enableSearch: true,
|
| 1838 |
enableAutoPlay: true,
|
| 1839 |
enableAnnotation: false,
|
|
@@ -1850,7 +2259,8 @@ HTML = """
|
|
| 1850 |
pageTextureSize: 1024, // ํ์ด์ง ํ
์ค์ฒ ํฌ๊ธฐ
|
| 1851 |
thumbnails: true, // ์ฌ๋ค์ผ ํ์ฑํ
|
| 1852 |
autoHideControls: false, // ์๋ ์จ๊น ๋นํ์ฑํ
|
| 1853 |
-
controlsTimeout: 8000 // ์ปจํธ๋กค ํ์ ์๊ฐ ์ฐ์ฅ
|
|
|
|
| 1854 |
}
|
| 1855 |
});
|
| 1856 |
|
|
@@ -1905,6 +2315,7 @@ HTML = """
|
|
| 1905 |
}
|
| 1906 |
$id('loadingPages').style.display = 'none';
|
| 1907 |
currentLoadingPdfPath = null;
|
|
|
|
| 1908 |
};
|
| 1909 |
|
| 1910 |
function toggle(showHome){
|
|
@@ -2019,12 +2430,18 @@ HTML = """
|
|
| 2019 |
const card = document.createElement('div');
|
| 2020 |
card.className = 'admin-card card fade-in';
|
| 2021 |
|
|
|
|
|
|
|
|
|
|
| 2022 |
// ์ธ๋ค์ผ ๋ฐ ์ ๋ณด
|
| 2023 |
card.innerHTML = `
|
| 2024 |
<div class="card-inner">
|
| 2025 |
${pdf.cached ? '<div class="cached-status">์บ์๋จ</div>' : ''}
|
| 2026 |
<img src="${thumbData.thumbnail || ''}" alt="${pdf.name}" loading="lazy">
|
| 2027 |
<p title="${pdf.name}">${pdf.name.length > 15 ? pdf.name.substring(0, 15) + '...' : pdf.name}</p>
|
|
|
|
|
|
|
|
|
|
| 2028 |
${isMainDisplayed ?
|
| 2029 |
`<button class="unfeature-btn" data-path="${pdf.path}">๋ฉ์ธ์์ ์ ๊ฑฐ</button>` :
|
| 2030 |
`<button class="feature-btn" data-path="${pdf.path}">๋ฉ์ธ์ ํ์</button>`}
|
|
@@ -2279,9 +2696,5 @@ HTML = """
|
|
| 2279 |
</html>
|
| 2280 |
"""
|
| 2281 |
|
| 2282 |
-
@app.get("/", response_class=HTMLResponse)
|
| 2283 |
-
async def root():
|
| 2284 |
-
return get_html_content()
|
| 2285 |
-
|
| 2286 |
if __name__ == "__main__":
|
| 2287 |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
|
|
|
|
| 1 |
+
from fastapi import FastAPI, BackgroundTasks, UploadFile, File, Form, Request, Query
|
| 2 |
+
from fastapi.responses import HTMLResponse, JSONResponse, Response, RedirectResponse
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
import pathlib, os, uvicorn, base64, json, shutil, uuid, time, urllib.parse
|
| 5 |
+
from typing import Dict, List, Any, Optional
|
| 6 |
import asyncio
|
| 7 |
import logging
|
| 8 |
import threading
|
|
|
|
| 31 |
if not CACHE_DIR.exists():
|
| 32 |
CACHE_DIR.mkdir(parents=True)
|
| 33 |
|
| 34 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ ๋๋ ํ ๋ฆฌ ๋ฐ ํ์ผ ์ค์
|
| 35 |
+
METADATA_DIR = pathlib.Path("/data/metadata") if os.path.exists("/data") else BASE / "metadata"
|
| 36 |
+
if not METADATA_DIR.exists():
|
| 37 |
+
METADATA_DIR.mkdir(parents=True)
|
| 38 |
+
PDF_METADATA_FILE = METADATA_DIR / "pdf_metadata.json"
|
| 39 |
+
|
| 40 |
# ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ
|
| 41 |
ADMIN_PASSWORD = os.getenv("PASSWORD", "admin") # ํ๊ฒฝ ๋ณ์์์ ๊ฐ์ ธ์ค๊ธฐ, ๊ธฐ๋ณธ๊ฐ์ ํ
์คํธ์ฉ
|
| 42 |
|
|
|
|
| 44 |
pdf_cache: Dict[str, Dict[str, Any]] = {}
|
| 45 |
# ์บ์ฑ ๋ฝ
|
| 46 |
cache_locks = {}
|
| 47 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ (ID to ๊ฒฝ๋ก ๋งคํ)
|
| 48 |
+
pdf_metadata: Dict[str, str] = {}
|
| 49 |
+
|
| 50 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ ๋ก๋
|
| 51 |
+
def load_pdf_metadata():
|
| 52 |
+
global pdf_metadata
|
| 53 |
+
if PDF_METADATA_FILE.exists():
|
| 54 |
+
try:
|
| 55 |
+
with open(PDF_METADATA_FILE, "r") as f:
|
| 56 |
+
pdf_metadata = json.load(f)
|
| 57 |
+
logger.info(f"PDF ๋ฉํ๋ฐ์ดํฐ ๋ก๋ ์๋ฃ: {len(pdf_metadata)} ํญ๋ชฉ")
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"๋ฉํ๋ฐ์ดํฐ ๋ก๋ ์ค๋ฅ: {e}")
|
| 60 |
+
pdf_metadata = {}
|
| 61 |
+
else:
|
| 62 |
+
pdf_metadata = {}
|
| 63 |
+
|
| 64 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
|
| 65 |
+
def save_pdf_metadata():
|
| 66 |
+
try:
|
| 67 |
+
with open(PDF_METADATA_FILE, "w") as f:
|
| 68 |
+
json.dump(pdf_metadata, f)
|
| 69 |
+
except Exception as e:
|
| 70 |
+
logger.error(f"๋ฉํ๋ฐ์ดํฐ ์ ์ฅ ์ค๋ฅ: {e}")
|
| 71 |
+
|
| 72 |
+
# PDF ID ์์ฑ (ํ์ผ๋ช
+ ํ์์คํฌํ ๊ธฐ๋ฐ)
|
| 73 |
+
# PDF ID ์์ฑ (ํ์ผ๋ช
+ ํ์์คํฌํ ๊ธฐ๋ฐ) - ๋ ๋จ์ํ๊ณ ์์ ํ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ
|
| 74 |
+
def generate_pdf_id(filename: str) -> str:
|
| 75 |
+
# ํ์ผ๋ช
์์ ํ์ฅ์ ์ ๊ฑฐ
|
| 76 |
+
base_name = os.path.splitext(filename)[0]
|
| 77 |
+
# ์์ ํ ๋ฌธ์์ด๋ก ๋ณํ (URL ์ธ์ฝ๋ฉ ๋์ ์ง์ ๋ณํ)
|
| 78 |
+
import re
|
| 79 |
+
safe_name = re.sub(r'[^\w\-_]', '_', base_name.replace(" ", "_"))
|
| 80 |
+
# ํ์์คํฌํ ์ถ๊ฐ๋ก ๊ณ ์ ์ฑ ๋ณด์ฅ
|
| 81 |
+
timestamp = int(time.time())
|
| 82 |
+
# ์งง์ ์์ ๋ฌธ์์ด ์ถ๊ฐ
|
| 83 |
+
random_suffix = uuid.uuid4().hex[:6]
|
| 84 |
+
return f"{safe_name}_{timestamp}_{random_suffix}"
|
| 85 |
+
|
| 86 |
+
|
| 87 |
|
| 88 |
# PDF ํ์ผ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ (๋ฉ์ธ ๋๋ ํ ๋ฆฌ์ฉ)
|
| 89 |
def get_pdf_files():
|
|
|
|
| 99 |
pdf_files = [f for f in PERMANENT_PDF_DIR.glob("*.pdf")]
|
| 100 |
return pdf_files
|
| 101 |
|
|
|
|
| 102 |
# PDF ์ธ๋ค์ผ ์์ฑ ๋ฐ ํ๋ก์ ํธ ๋ฐ์ดํฐ ์ค๋น
|
| 103 |
def generate_pdf_projects():
|
| 104 |
projects_data = []
|
|
|
|
| 120 |
|
| 121 |
# ์ค๋ณต ์ ๊ฑฐ๋ ํ์ผ๋ค๋ก ํ๋ก์ ํธ ๋ฐ์ดํฐ ์์ฑ
|
| 122 |
for pdf_file in unique_files.values():
|
| 123 |
+
# ํด๋น ํ์ผ์ PDF ID ์ฐพ๊ธฐ
|
| 124 |
+
pdf_id = None
|
| 125 |
+
for pid, path in pdf_metadata.items():
|
| 126 |
+
if os.path.basename(path) == pdf_file.name:
|
| 127 |
+
pdf_id = pid
|
| 128 |
+
break
|
| 129 |
+
|
| 130 |
+
# ID๊ฐ ์์ผ๋ฉด ์๋ก ์์ฑํ๊ณ ๋ฉํ๋ฐ์ดํฐ์ ์ถ๊ฐ
|
| 131 |
+
if not pdf_id:
|
| 132 |
+
pdf_id = generate_pdf_id(pdf_file.name)
|
| 133 |
+
pdf_metadata[pdf_id] = str(pdf_file)
|
| 134 |
+
save_pdf_metadata()
|
| 135 |
+
|
| 136 |
projects_data.append({
|
| 137 |
"path": str(pdf_file),
|
| 138 |
"name": pdf_file.stem,
|
| 139 |
+
"id": pdf_id,
|
| 140 |
"cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
|
| 141 |
})
|
| 142 |
|
| 143 |
return projects_data
|
| 144 |
|
|
|
|
| 145 |
# ์บ์ ํ์ผ ๊ฒฝ๋ก ์์ฑ
|
| 146 |
def get_cache_path(pdf_name: str):
|
| 147 |
return CACHE_DIR / f"{pdf_name}_cache.json"
|
|
|
|
| 300 |
pdf_cache[pdf_name]["status"] = "error"
|
| 301 |
pdf_cache[pdf_name]["error"] = str(e)
|
| 302 |
|
| 303 |
+
# PDF ID๋ก PDF ๊ฒฝ๋ก ์ฐพ๊ธฐ
|
| 304 |
+
# PDF ID๋ก PDF ๊ฒฝ๋ก ์ฐพ๊ธฐ (๊ฐ์ ๋ ๊ฒ์ ๋ก์ง)
|
| 305 |
+
def get_pdf_path_by_id(pdf_id: str) -> str:
|
| 306 |
+
logger.info(f"PDF ID๋ก ํ์ผ ์กฐํ: {pdf_id}")
|
| 307 |
+
|
| 308 |
+
# 1. ๋ฉํ๋ฐ์ดํฐ์์ ์ง์ ID๋ก ๊ฒ์
|
| 309 |
+
if pdf_id in pdf_metadata:
|
| 310 |
+
path = pdf_metadata[pdf_id]
|
| 311 |
+
# ํ์ผ ์กด์ฌ ํ์ธ
|
| 312 |
+
if os.path.exists(path):
|
| 313 |
+
return path
|
| 314 |
+
|
| 315 |
+
# ํ์ผ์ด ์ด๋ํ์ ์ ์์ผ๋ฏ๋ก ํ์ผ๋ช
์ผ๋ก ๊ฒ์
|
| 316 |
+
filename = os.path.basename(path)
|
| 317 |
+
|
| 318 |
+
# ์๊ตฌ ์ ์ฅ์์์ ๊ฒ์
|
| 319 |
+
perm_path = PERMANENT_PDF_DIR / filename
|
| 320 |
+
if perm_path.exists():
|
| 321 |
+
# ๋ฉํ๋ฐ์ดํฐ ์
๋ฐ์ดํธ
|
| 322 |
+
pdf_metadata[pdf_id] = str(perm_path)
|
| 323 |
+
save_pdf_metadata()
|
| 324 |
+
return str(perm_path)
|
| 325 |
+
|
| 326 |
+
# ๋ฉ์ธ ๋๋ ํ ๋ฆฌ์์ ๊ฒ์
|
| 327 |
+
main_path = PDF_DIR / filename
|
| 328 |
+
if main_path.exists():
|
| 329 |
+
# ๋ฉํ๋ฐ์ดํฐ ์
๋ฐ์ดํธ
|
| 330 |
+
pdf_metadata[pdf_id] = str(main_path)
|
| 331 |
+
save_pdf_metadata()
|
| 332 |
+
return str(main_path)
|
| 333 |
+
|
| 334 |
+
# 2. ํ์ผ๋ช
๋ถ๋ถ๋ง ์ถ์ถํ์ฌ ๋ชจ๋ PDF ํ์ผ ๊ฒ์
|
| 335 |
+
try:
|
| 336 |
+
# ID ํ์: filename_timestamp_random
|
| 337 |
+
# ํ์ผ๋ช
๋ถ๋ถ๋ง ์ถ์ถ
|
| 338 |
+
name_part = pdf_id.split('_')[0] if '_' in pdf_id else pdf_id
|
| 339 |
+
|
| 340 |
+
# ๋ชจ๋ PDF ํ์ผ ๊ฒ์
|
| 341 |
+
for file_path in get_pdf_files() + get_permanent_pdf_files():
|
| 342 |
+
# ํ์ผ๋ช
์ด ID์ ์์ ๋ถ๋ถ๊ณผ ์ผ์นํ๋ฉด
|
| 343 |
+
file_basename = os.path.basename(file_path)
|
| 344 |
+
if file_basename.startswith(name_part) or file_path.stem.startswith(name_part):
|
| 345 |
+
# ID ๋งคํ ์
๋ฐ์ดํธ
|
| 346 |
+
pdf_metadata[pdf_id] = str(file_path)
|
| 347 |
+
save_pdf_metadata()
|
| 348 |
+
return str(file_path)
|
| 349 |
+
except Exception as e:
|
| 350 |
+
logger.error(f"ํ์ผ๋ช
๊ฒ์ ์ค ์ค๋ฅ: {e}")
|
| 351 |
+
|
| 352 |
+
# 3. ๋ชจ๋ PDF ํ์ผ์ ๋ํด ๋ฉํ๋ฐ์ดํฐ ํ์ธ
|
| 353 |
+
for pid, path in pdf_metadata.items():
|
| 354 |
+
if os.path.exists(path):
|
| 355 |
+
file_basename = os.path.basename(path)
|
| 356 |
+
# ์ ์ฌํ ํ์ผ๋ช
์ ๊ฐ์ง ๊ฒฝ์ฐ
|
| 357 |
+
if pdf_id in pid or pid in pdf_id:
|
| 358 |
+
pdf_metadata[pdf_id] = path
|
| 359 |
+
save_pdf_metadata()
|
| 360 |
+
return path
|
| 361 |
+
|
| 362 |
+
return None
|
| 363 |
+
|
| 364 |
# ์์ ์ ๋ชจ๋ PDF ํ์ผ ์บ์ฑ
|
| 365 |
async def init_cache_all_pdfs():
|
| 366 |
logger.info("PDF ์บ์ฑ ์์
์์")
|
| 367 |
|
| 368 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ ๋ก๋
|
| 369 |
+
load_pdf_metadata()
|
| 370 |
+
|
| 371 |
# ๋ฉ์ธ ๋ฐ ์๊ตฌ ๋๋ ํ ๋ฆฌ์์ PDF ํ์ผ ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ
|
| 372 |
pdf_files = get_pdf_files() + get_permanent_pdf_files()
|
| 373 |
|
|
|
|
| 375 |
unique_pdf_paths = set(str(p) for p in pdf_files)
|
| 376 |
pdf_files = [pathlib.Path(p) for p in unique_pdf_paths]
|
| 377 |
|
| 378 |
+
# ํ์ผ ๊ธฐ๋ฐ ๋ฉํ๋ฐ์ดํฐ ์
๋ฐ์ดํธ
|
| 379 |
+
for pdf_file in pdf_files:
|
| 380 |
+
# ID๊ฐ ์๋ ํ์ผ์ ๋ํด ID ์์ฑ
|
| 381 |
+
found = False
|
| 382 |
+
for pid, path in pdf_metadata.items():
|
| 383 |
+
if os.path.basename(path) == pdf_file.name:
|
| 384 |
+
found = True
|
| 385 |
+
# ๊ฒฝ๋ก ์
๋ฐ์ดํธ ํ์ํ ๊ฒฝ์ฐ
|
| 386 |
+
if not os.path.exists(path):
|
| 387 |
+
pdf_metadata[pid] = str(pdf_file)
|
| 388 |
+
break
|
| 389 |
+
|
| 390 |
+
if not found:
|
| 391 |
+
pdf_id = generate_pdf_id(pdf_file.name)
|
| 392 |
+
pdf_metadata[pdf_id] = str(pdf_file)
|
| 393 |
+
|
| 394 |
+
# ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
|
| 395 |
+
save_pdf_metadata()
|
| 396 |
+
|
| 397 |
# ์ด๋ฏธ ์บ์๋ PDF ํ์ผ ๋ก๋ (๋น ๋ฅธ ์์์ ์ํด ๋จผ์ ์ํ)
|
| 398 |
for cache_file in CACHE_DIR.glob("*_cache.json"):
|
| 399 |
try:
|
|
|
|
| 416 |
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์์ ํจ์
|
| 417 |
@app.on_event("startup")
|
| 418 |
async def startup_event():
|
| 419 |
+
# PDF ๋ฉํ๋ฐ์ดํฐ ๋ก๋
|
| 420 |
+
load_pdf_metadata()
|
| 421 |
+
|
| 422 |
+
# ๋๋ฝ๋ PDF ํ์ผ์ ๋ํ ๋ฉํ๋ฐ์ดํฐ ์์ฑ
|
| 423 |
+
for pdf_file in get_pdf_files() + get_permanent_pdf_files():
|
| 424 |
+
found = False
|
| 425 |
+
for pid, path in pdf_metadata.items():
|
| 426 |
+
if os.path.basename(path) == pdf_file.name:
|
| 427 |
+
found = True
|
| 428 |
+
# ๊ฒฝ๋ก ์
๋ฐ์ดํธ
|
| 429 |
+
if not os.path.exists(path):
|
| 430 |
+
pdf_metadata[pid] = str(pdf_file)
|
| 431 |
+
break
|
| 432 |
+
|
| 433 |
+
if not found:
|
| 434 |
+
# ์ ID ์์ฑ ๋ฐ ๋ฉํ๋ฐ์ดํฐ์ ์ถ๊ฐ
|
| 435 |
+
pdf_id = generate_pdf_id(pdf_file.name)
|
| 436 |
+
pdf_metadata[pdf_id] = str(pdf_file)
|
| 437 |
+
|
| 438 |
+
# ๋ณ๊ฒฝ์ฌํญ ์ ์ฅ
|
| 439 |
+
save_pdf_metadata()
|
| 440 |
+
|
| 441 |
# ๋ฐฑ๊ทธ๋ผ์ด๋ ํ์คํฌ๋ก ์บ์ฑ ์คํ
|
| 442 |
asyncio.create_task(init_cache_all_pdfs())
|
| 443 |
|
|
|
|
| 453 |
projects_data = []
|
| 454 |
|
| 455 |
for pdf_file in pdf_files:
|
| 456 |
+
# PDF ID ์ฐพ๊ธฐ
|
| 457 |
+
pdf_id = None
|
| 458 |
+
for pid, path in pdf_metadata.items():
|
| 459 |
+
if os.path.basename(path) == pdf_file.name:
|
| 460 |
+
pdf_id = pid
|
| 461 |
+
break
|
| 462 |
+
|
| 463 |
+
# ID๊ฐ ์์ผ๋ฉด ์์ฑ
|
| 464 |
+
if not pdf_id:
|
| 465 |
+
pdf_id = generate_pdf_id(pdf_file.name)
|
| 466 |
+
pdf_metadata[pdf_id] = str(pdf_file)
|
| 467 |
+
save_pdf_metadata()
|
| 468 |
+
|
| 469 |
projects_data.append({
|
| 470 |
"path": str(pdf_file),
|
| 471 |
"name": pdf_file.stem,
|
| 472 |
+
"id": pdf_id,
|
| 473 |
"cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
|
| 474 |
})
|
| 475 |
|
| 476 |
return projects_data
|
| 477 |
|
| 478 |
+
# API ์๋ํฌ์ธํธ: PDF ID๋ก ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
|
| 479 |
+
@app.get("/api/pdf-info-by-id/{pdf_id}")
|
| 480 |
+
async def get_pdf_info_by_id(pdf_id: str):
|
| 481 |
+
pdf_path = get_pdf_path_by_id(pdf_id)
|
| 482 |
+
if pdf_path:
|
| 483 |
+
pdf_file = pathlib.Path(pdf_path)
|
| 484 |
+
return {
|
| 485 |
+
"path": pdf_path,
|
| 486 |
+
"name": pdf_file.stem,
|
| 487 |
+
"id": pdf_id,
|
| 488 |
+
"exists": True,
|
| 489 |
+
"cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
|
| 490 |
+
}
|
| 491 |
+
return {"exists": False, "error": "PDF๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค"}
|
| 492 |
+
|
| 493 |
# API ์๋ํฌ์ธํธ: PDF ์ธ๋ค์ผ ์ ๊ณต (์ต์ ํ)
|
| 494 |
@app.get("/api/pdf-thumbnail")
|
| 495 |
async def get_pdf_thumbnail(path: str):
|
|
|
|
| 614 |
logger.error(f"PDF ์ฝํ
์ธ ๋ก๋ ์ค๋ฅ: {str(e)}\n{error_details}")
|
| 615 |
return JSONResponse(content={"error": str(e)}, status_code=500)
|
| 616 |
|
|
|
|
| 617 |
# PDF ์
๋ก๋ ์๋ํฌ์ธํธ - ์๊ตฌ ์ ์ฅ์์ ์ ์ฅ ๋ฐ ๋ฉ์ธ ํ๋ฉด์ ์๋ ํ์
|
| 618 |
@app.post("/api/upload-pdf")
|
| 619 |
async def upload_pdf(file: UploadFile = File(...)):
|
|
|
|
| 637 |
with open(PDF_DIR / file.filename, "wb") as buffer:
|
| 638 |
buffer.write(content)
|
| 639 |
|
| 640 |
+
# PDF ID ์์ฑ ๋ฐ ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
|
| 641 |
+
pdf_id = generate_pdf_id(file.filename)
|
| 642 |
+
pdf_metadata[pdf_id] = str(file_path)
|
| 643 |
+
save_pdf_metadata()
|
| 644 |
+
|
| 645 |
# ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์บ์ฑ ์์
|
| 646 |
asyncio.create_task(cache_pdf(str(file_path)))
|
| 647 |
|
| 648 |
return JSONResponse(
|
| 649 |
+
content={
|
| 650 |
+
"success": True,
|
| 651 |
+
"path": str(file_path),
|
| 652 |
+
"name": file_path.stem,
|
| 653 |
+
"id": pdf_id,
|
| 654 |
+
"viewUrl": f"/view/{pdf_id}"
|
| 655 |
+
},
|
| 656 |
status_code=200
|
| 657 |
)
|
| 658 |
except Exception as e:
|
|
|
|
| 664 |
status_code=500
|
| 665 |
)
|
| 666 |
|
|
|
|
| 667 |
# ๊ด๋ฆฌ์ ์ธ์ฆ ์๋ํฌ์ธํธ
|
| 668 |
@app.post("/api/admin-login")
|
| 669 |
async def admin_login(password: str = Form(...)):
|
|
|
|
| 692 |
if pdf_name in pdf_cache:
|
| 693 |
del pdf_cache[pdf_name]
|
| 694 |
|
| 695 |
+
# ๋ฉํ๋ฐ์ดํฐ์์ ํด๋น ํ์ผ ID ์ ๊ฑฐ
|
| 696 |
+
to_remove = []
|
| 697 |
+
for pid, fpath in pdf_metadata.items():
|
| 698 |
+
if os.path.basename(fpath) == pdf_file.name:
|
| 699 |
+
to_remove.append(pid)
|
| 700 |
+
|
| 701 |
+
for pid in to_remove:
|
| 702 |
+
del pdf_metadata[pid]
|
| 703 |
+
|
| 704 |
+
save_pdf_metadata()
|
| 705 |
+
|
| 706 |
return {"success": True}
|
| 707 |
except Exception as e:
|
| 708 |
logger.error(f"PDF ์ญ์ ์ค๋ฅ: {str(e)}")
|
|
|
|
| 740 |
logger.error(f"PDF ํ์ ํด์ ์ค๋ฅ: {str(e)}")
|
| 741 |
return {"success": False, "message": str(e)}
|
| 742 |
|
| 743 |
+
# ์ง์ PDF ๋ทฐ์ด URL ์ ๊ทผ์ฉ ๋ผ์ฐํธ
|
| 744 |
+
@app.get("/view/{pdf_id}")
|
| 745 |
+
async def view_pdf_by_id(pdf_id: str):
|
| 746 |
+
# PDF ID ์ ํจํ์ง ํ์ธ
|
| 747 |
+
pdf_path = get_pdf_path_by_id(pdf_id)
|
| 748 |
+
|
| 749 |
+
if not pdf_path:
|
| 750 |
+
# ์ผ๋จ ๋ชจ๋ PDF ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ก๋ํ๊ณ ์ฌ์๋
|
| 751 |
+
load_pdf_metadata()
|
| 752 |
+
pdf_path = get_pdf_path_by_id(pdf_id)
|
| 753 |
+
|
| 754 |
+
if not pdf_path:
|
| 755 |
+
# ๋ชจ๋ PDF ํ์ผ์ ์ง์ ์ค์บํ์ฌ ์ ์ฌํ ์ด๋ฆ ์ฐพ๊ธฐ
|
| 756 |
+
for file_path in get_pdf_files() + get_permanent_pdf_files():
|
| 757 |
+
name_part = pdf_id.split('_')[0] if '_' in pdf_id else pdf_id
|
| 758 |
+
if file_path.stem.startswith(name_part):
|
| 759 |
+
pdf_metadata[pdf_id] = str(file_path)
|
| 760 |
+
save_pdf_metadata()
|
| 761 |
+
pdf_path = str(file_path)
|
| 762 |
+
break
|
| 763 |
+
|
| 764 |
+
if not pdf_path:
|
| 765 |
+
return HTMLResponse(
|
| 766 |
+
content=f"<html><body><h1>PDF๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค</h1><p>ID: {pdf_id}</p><a href='/'>ํ์ผ๋ก ๋์๊ฐ๊ธฐ</a></body></html>",
|
| 767 |
+
status_code=404
|
| 768 |
+
)
|
| 769 |
+
|
| 770 |
+
# ๋ฉ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธํ๋, PDF ID ํ๋ผ๋ฏธํฐ ์ถ๊ฐ
|
| 771 |
+
return get_html_content(pdf_id=pdf_id)
|
| 772 |
+
|
| 773 |
# HTML ํ์ผ ์ฝ๊ธฐ ํจ์
|
| 774 |
+
def get_html_content(pdf_id: str = None):
|
| 775 |
html_path = BASE / "flipbook_template.html"
|
| 776 |
+
content = ""
|
| 777 |
if html_path.exists():
|
| 778 |
with open(html_path, "r", encoding="utf-8") as f:
|
| 779 |
+
content = f.read()
|
| 780 |
+
else:
|
| 781 |
+
content = HTML # ๊ธฐ๋ณธ HTML ์ฌ์ฉ
|
| 782 |
+
|
| 783 |
+
# PDF ID๊ฐ ์ ๊ณต๋ ๊ฒฝ์ฐ, ์๋ ๋ก๋ ์คํฌ๋ฆฝํธ ์ถ๊ฐ
|
| 784 |
+
if pdf_id:
|
| 785 |
+
auto_load_script = f"""
|
| 786 |
+
<script>
|
| 787 |
+
// ํ์ด์ง ๋ก๋ ์ ์๋์ผ๋ก ํด๋น PDF ์ด๊ธฐ
|
| 788 |
+
document.addEventListener('DOMContentLoaded', async function() {{
|
| 789 |
+
try {{
|
| 790 |
+
// PDF ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
|
| 791 |
+
const response = await fetch('/api/pdf-info-by-id/{pdf_id}');
|
| 792 |
+
const pdfInfo = await response.json();
|
| 793 |
+
|
| 794 |
+
if (pdfInfo.exists && pdfInfo.path) {{
|
| 795 |
+
// ์ฝ๊ฐ์ ์ง์ฐ ํ PDF ๋ทฐ์ด ์ด๊ธฐ (UI๊ฐ ์ค๋น๋ ํ)
|
| 796 |
+
setTimeout(() => {{
|
| 797 |
+
openPdfById('{pdf_id}', pdfInfo.path, pdfInfo.cached);
|
| 798 |
+
}}, 500);
|
| 799 |
+
}} else {{
|
| 800 |
+
showError("์์ฒญํ PDF๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
|
| 801 |
+
}}
|
| 802 |
+
}} catch (e) {{
|
| 803 |
+
console.error("์๋ PDF ๋ก๋ ์ค๋ฅ:", e);
|
| 804 |
+
}}
|
| 805 |
+
}});
|
| 806 |
+
</script>
|
| 807 |
+
"""
|
| 808 |
+
|
| 809 |
+
# body ์ข
๋ฃ ํ๊ทธ ์ ์ ์คํฌ๋ฆฝํธ ์ฝ์
|
| 810 |
+
content = content.replace("</body>", auto_load_script + "</body>")
|
| 811 |
+
|
| 812 |
+
return HTMLResponse(content=content)
|
| 813 |
+
|
| 814 |
+
@app.get("/", response_class=HTMLResponse)
|
| 815 |
+
async def root(request: Request, pdf_id: Optional[str] = Query(None)):
|
| 816 |
+
# PDF ID๊ฐ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ ๊ณต๋ ๊ฒฝ์ฐ /view/{pdf_id}๋ก ๋ฆฌ๋ค์ด๋ ํธ
|
| 817 |
+
if pdf_id:
|
| 818 |
+
return RedirectResponse(url=f"/view/{pdf_id}")
|
| 819 |
+
return get_html_content()
|
| 820 |
|
| 821 |
# HTML ๋ฌธ์์ด (UI ์์ ๋ฒ์ )
|
| 822 |
HTML = """
|
|
|
|
| 1580 |
// ํ์ฌ ํ์ด์ง ๋ก๋ฉ ์ํ
|
| 1581 |
let currentLoadingPdfPath = null;
|
| 1582 |
let pageLoadingInterval = null;
|
| 1583 |
+
|
| 1584 |
+
// ํ์ฌ ์ด๋ฆฐ PDF์ ID
|
| 1585 |
+
let currentPdfId = null;
|
| 1586 |
|
| 1587 |
/* ๐ ์ค๋์ค unlock โ ๋ด์ฅ Audio ์ ๊ฐ์ MP3 ๊ฒฝ๋ก ์ฌ์ฉ */
|
| 1588 |
['click','touchstart'].forEach(evt=>{
|
|
|
|
| 1656 |
await loadServerPDFs();
|
| 1657 |
|
| 1658 |
// ์ฑ๊ณต ๋ฉ์์ง
|
| 1659 |
+
showMessage("PDF๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์
๋ก๋๋์์ต๋๋ค! ๊ณต์ URL: " + result.viewUrl);
|
| 1660 |
} else {
|
| 1661 |
hideLoading();
|
| 1662 |
showError("์
๋ก๋ ์คํจ: " + (result.message || "์ ์ ์๋ ์ค๋ฅ"));
|
|
|
|
| 1667 |
showError("PDF ์
๋ก๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.");
|
| 1668 |
}
|
| 1669 |
}
|
| 1670 |
+
|
| 1671 |
+
function addCard(i, thumb, title, isCached = false, pdfId = null) {
|
| 1672 |
+
const d = document.createElement('div');
|
| 1673 |
+
d.className = 'card fade-in';
|
| 1674 |
+
d.onclick = () => open(i);
|
| 1675 |
+
|
| 1676 |
+
// PDF ID๊ฐ ์์ผ๋ฉด ๋ฐ์ดํฐ ์์ฑ์ผ๋ก ์ ์ฅ
|
| 1677 |
+
if (pdfId) {
|
| 1678 |
+
d.dataset.pdfId = pdfId;
|
| 1679 |
+
}
|
| 1680 |
+
|
| 1681 |
+
// ์ ๋ชฉ ์ฒ๋ฆฌ
|
| 1682 |
+
const displayTitle = title ?
|
| 1683 |
+
(title.length > 15 ? title.substring(0, 15) + '...' : title) :
|
| 1684 |
+
'ํ๋ก์ ํธ ' + (i+1);
|
| 1685 |
+
|
| 1686 |
+
// ์บ์ ์ํ ๋ฑ์ง ์ถ๊ฐ
|
| 1687 |
+
const cachedBadge = isCached ?
|
| 1688 |
+
'<div class="cached-status">์บ์๋จ</div>' : '';
|
| 1689 |
+
|
| 1690 |
+
// ๋ฐ๋ก๊ฐ๊ธฐ ๋งํฌ ์ถ๊ฐ (PDF ID๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง)
|
| 1691 |
+
const linkHtml = pdfId ?
|
| 1692 |
+
`<div style="position: absolute; bottom: 55px; left: 50%; transform: translateX(-50%); z-index:5;">
|
| 1693 |
+
<a href="/view/${pdfId}" target="_blank" style="color:#4a6ee0; font-size:11px;">Shaer Link</a>
|
| 1694 |
+
</div>` : '';
|
| 1695 |
+
|
| 1696 |
+
d.innerHTML = `
|
| 1697 |
+
<div class="card-inner">
|
| 1698 |
+
${cachedBadge}
|
| 1699 |
+
<img src="${thumb}" alt="${displayTitle}" loading="lazy">
|
| 1700 |
+
${linkHtml}
|
| 1701 |
+
<p title="${title || 'ํ๋ก์ ํธ ' + (i+1)}">${displayTitle}</p>
|
| 1702 |
+
</div>
|
| 1703 |
+
`;
|
| 1704 |
+
grid.appendChild(d);
|
| 1705 |
+
|
| 1706 |
+
// ํ๋ก์ ํธ๊ฐ ์์ผ๋ฉด 'ํ๋ก์ ํธ ์์' ๋ฉ์์ง ์จ๊ธฐ๊ธฐ
|
| 1707 |
+
$id('noProjects').style.display = 'none';
|
| 1708 |
+
}
|
| 1709 |
+
|
| 1710 |
+
|
| 1711 |
|
| 1712 |
/* โโ ํ๋ก์ ํธ ์ ์ฅ โโ */
|
| 1713 |
+
function save(pages, title, isCached = false, pdfId = null) {
|
| 1714 |
+
const id = projects.push(pages) - 1;
|
| 1715 |
+
addCard(id, pages[0].thumb, title, isCached, pdfId);
|
| 1716 |
}
|
| 1717 |
|
| 1718 |
/* โโ ์๋ฒ PDF ๋ก๋ ๋ฐ ์บ์ ์ํ ํ์ธ โโ */
|
|
|
|
| 1764 |
return {
|
| 1765 |
pages,
|
| 1766 |
name: project.name,
|
| 1767 |
+
isCached,
|
| 1768 |
+
id: project.id
|
| 1769 |
};
|
| 1770 |
}
|
| 1771 |
} catch (err) {
|
|
|
|
| 1780 |
|
| 1781 |
// ์ฑ๊ณต์ ์ผ๋ก ๊ฐ์ ธ์จ ๊ฒฐ๊ณผ๋ง ํ์
|
| 1782 |
results.filter(result => result !== null).forEach(result => {
|
| 1783 |
+
save(result.pages, result.name, result.isCached, result.id);
|
| 1784 |
});
|
| 1785 |
|
| 1786 |
hideLoading();
|
|
|
|
| 1865 |
}
|
| 1866 |
}
|
| 1867 |
|
| 1868 |
+
/* โโ PDF ID๋ก PDF ์ด๊ธฐ โโ */
|
| 1869 |
+
async function openPdfById(pdfId, pdfPath, isCached = false) {
|
| 1870 |
+
try {
|
| 1871 |
+
// ๋จผ์ ํ ํ๋ฉด์์ ์นด๋๋ฅผ ์ฐพ์์ ํด๋ฆญํ๋ ๋ฐฉ๋ฒ ์๋
|
| 1872 |
+
let foundCard = false;
|
| 1873 |
+
const cards = document.querySelectorAll('.card');
|
| 1874 |
+
|
| 1875 |
+
for (let i = 0; i < cards.length; i++) {
|
| 1876 |
+
if (cards[i].dataset.pdfId === pdfId) {
|
| 1877 |
+
cards[i].click();
|
| 1878 |
+
foundCard = true;
|
| 1879 |
+
break;
|
| 1880 |
+
}
|
| 1881 |
+
}
|
| 1882 |
+
|
| 1883 |
+
// ์นด๋๋ฅผ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ ์ง์ ์คํ
|
| 1884 |
+
if (!foundCard) {
|
| 1885 |
+
toggle(false);
|
| 1886 |
+
showLoading("PDF ์ค๋น ์ค...");
|
| 1887 |
+
|
| 1888 |
+
let pages = [];
|
| 1889 |
+
|
| 1890 |
+
// ์ด๋ฏธ ์บ์๋ ๊ฒฝ์ฐ ์บ์๋ ๋ฐ์ดํฐ ์ฌ์ฉ
|
| 1891 |
+
if (isCached) {
|
| 1892 |
+
try {
|
| 1893 |
+
const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(pdfPath)}`);
|
| 1894 |
+
const cachedData = await response.json();
|
| 1895 |
+
|
| 1896 |
+
if (cachedData.status === "completed" && cachedData.pages) {
|
| 1897 |
+
hideLoading();
|
| 1898 |
+
createFlipBook(cachedData.pages);
|
| 1899 |
+
// ํ์ฌ ์ด๋ฆฐ PDF์ ID ์ ์ฅ
|
| 1900 |
+
currentPdfId = pdfId;
|
| 1901 |
+
return;
|
| 1902 |
+
}
|
| 1903 |
+
} catch (error) {
|
| 1904 |
+
console.error("์บ์ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:", error);
|
| 1905 |
+
}
|
| 1906 |
+
}
|
| 1907 |
+
|
| 1908 |
+
// ์ธ๋ค์ผ ๊ฐ์ ธ์ค๊ธฐ
|
| 1909 |
+
try {
|
| 1910 |
+
const thumbResponse = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(pdfPath)}`);
|
| 1911 |
+
const thumbData = await thumbResponse.json();
|
| 1912 |
+
|
| 1913 |
+
if (thumbData.thumbnail) {
|
| 1914 |
+
pages = [{
|
| 1915 |
+
src: thumbData.thumbnail,
|
| 1916 |
+
thumb: thumbData.thumbnail,
|
| 1917 |
+
path: pdfPath,
|
| 1918 |
+
cached: isCached
|
| 1919 |
+
}];
|
| 1920 |
+
}
|
| 1921 |
+
} catch (error) {
|
| 1922 |
+
console.error("์ธ๋ค์ผ ๋ก๋ ์คํจ:", error);
|
| 1923 |
+
}
|
| 1924 |
+
|
| 1925 |
+
// ์ผ๋จ ๊ธฐ๋ณธ ํ์ด์ง ์ถ๊ฐ
|
| 1926 |
+
if (pages.length === 0) {
|
| 1927 |
+
pages = [{
|
| 1928 |
+
path: pdfPath,
|
| 1929 |
+
cached: isCached
|
| 1930 |
+
}];
|
| 1931 |
+
}
|
| 1932 |
+
|
| 1933 |
+
// ํ๋ก์ ํธ์ ์ถ๊ฐํ๊ณ ๋ทฐ์ด ์คํ
|
| 1934 |
+
const projectId = projects.push(pages) - 1;
|
| 1935 |
+
hideLoading();
|
| 1936 |
+
open(projectId);
|
| 1937 |
+
|
| 1938 |
+
// ํ์ฌ ์ด๋ฆฐ PDF์ ID ์ ์ฅ
|
| 1939 |
+
currentPdfId = pdfId;
|
| 1940 |
+
}
|
| 1941 |
+
} catch (error) {
|
| 1942 |
+
console.error("PDF ID๋ก ์ด๊ธฐ ์คํจ:", error);
|
| 1943 |
+
hideLoading();
|
| 1944 |
+
showError("PDF๋ฅผ ์ด ์ ์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.");
|
| 1945 |
+
}
|
| 1946 |
+
}
|
| 1947 |
+
|
| 1948 |
+
/* โโ ํ์ฌ PDF์ ๊ณ ์ URL ์์ฑ ๋ฐ ๋ณต์ฌ โโ */
|
| 1949 |
+
function copyPdfShareUrl() {
|
| 1950 |
+
if (!currentPdfId) {
|
| 1951 |
+
showError("๊ณต์ ํ PDF๊ฐ ์์ต๋๋ค.");
|
| 1952 |
+
return;
|
| 1953 |
+
}
|
| 1954 |
+
|
| 1955 |
+
// ํ์ฌ ๋๋ฉ์ธ ๊ธฐ๋ฐ ์ ์ฒด URL ์์ฑ
|
| 1956 |
+
const shareUrl = `${window.location.origin}/view/${currentPdfId}`;
|
| 1957 |
+
|
| 1958 |
+
// ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ
|
| 1959 |
+
navigator.clipboard.writeText(shareUrl)
|
| 1960 |
+
.then(() => {
|
| 1961 |
+
showMessage("PDF ๋งํฌ๊ฐ ๋ณต์ฌ๋์์ต๋๋ค!");
|
| 1962 |
+
})
|
| 1963 |
+
.catch(err => {
|
| 1964 |
+
console.error("ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ ์คํจ:", err);
|
| 1965 |
+
showError("๋งํฌ ๋ณต์ฌ์ ์คํจํ์ต๋๋ค.");
|
| 1966 |
+
});
|
| 1967 |
+
}
|
| 1968 |
+
|
| 1969 |
/* โโ ์นด๋ โ FlipBook โโ */
|
| 1970 |
async function open(i) {
|
| 1971 |
toggle(false);
|
| 1972 |
const pages = projects[i];
|
| 1973 |
|
| 1974 |
+
// PDF ID ์ฐพ๊ธฐ ๋ฐ ์ ์ฅ
|
| 1975 |
+
const card = document.querySelectorAll('.card')[i];
|
| 1976 |
+
if (card && card.dataset.pdfId) {
|
| 1977 |
+
currentPdfId = card.dataset.pdfId;
|
| 1978 |
+
} else {
|
| 1979 |
+
currentPdfId = null;
|
| 1980 |
+
}
|
| 1981 |
+
|
| 1982 |
// ๊ธฐ์กด FlipBook ์ ๋ฆฌ
|
| 1983 |
if(fb) {
|
| 1984 |
fb.destroy();
|
|
|
|
| 2242 |
enableDownload: false,
|
| 2243 |
enablePrint: false,
|
| 2244 |
enableZoom: true,
|
| 2245 |
+
enableShare: true, // ๊ณต์ ๋ฒํผ ํ์ฑํ
|
| 2246 |
enableSearch: true,
|
| 2247 |
enableAutoPlay: true,
|
| 2248 |
enableAnnotation: false,
|
|
|
|
| 2259 |
pageTextureSize: 1024, // ํ์ด์ง ํ
์ค์ฒ ํฌ๊ธฐ
|
| 2260 |
thumbnails: true, // ์ฌ๋ค์ผ ํ์ฑํ
|
| 2261 |
autoHideControls: false, // ์๋ ์จ๊น ๋นํ์ฑํ
|
| 2262 |
+
controlsTimeout: 8000, // ์ปจํธ๋กค ํ์ ์๊ฐ ์ฐ์ฅ
|
| 2263 |
+
shareHandler: copyPdfShareUrl // ๊ณต์ ํธ๋ค๋ฌ ์ค์
|
| 2264 |
}
|
| 2265 |
});
|
| 2266 |
|
|
|
|
| 2315 |
}
|
| 2316 |
$id('loadingPages').style.display = 'none';
|
| 2317 |
currentLoadingPdfPath = null;
|
| 2318 |
+
currentPdfId = null;
|
| 2319 |
};
|
| 2320 |
|
| 2321 |
function toggle(showHome){
|
|
|
|
| 2430 |
const card = document.createElement('div');
|
| 2431 |
card.className = 'admin-card card fade-in';
|
| 2432 |
|
| 2433 |
+
// ๊ณ ์ URL ์์ฑ
|
| 2434 |
+
const viewUrl = `${window.location.origin}/view/${pdf.id}`;
|
| 2435 |
+
|
| 2436 |
// ์ธ๋ค์ผ ๋ฐ ์ ๋ณด
|
| 2437 |
card.innerHTML = `
|
| 2438 |
<div class="card-inner">
|
| 2439 |
${pdf.cached ? '<div class="cached-status">์บ์๋จ</div>' : ''}
|
| 2440 |
<img src="${thumbData.thumbnail || ''}" alt="${pdf.name}" loading="lazy">
|
| 2441 |
<p title="${pdf.name}">${pdf.name.length > 15 ? pdf.name.substring(0, 15) + '...' : pdf.name}</p>
|
| 2442 |
+
<div style="position: absolute; bottom: 130px; left: 50%; transform: translateX(-50%); z-index:10;">
|
| 2443 |
+
<a href="${viewUrl}" target="_blank" style="color:#4a6ee0; font-size:12px;">๋ฐ๋ก๊ฐ๊ธฐ ๋งํฌ</a>
|
| 2444 |
+
</div>
|
| 2445 |
${isMainDisplayed ?
|
| 2446 |
`<button class="unfeature-btn" data-path="${pdf.path}">๋ฉ์ธ์์ ์ ๊ฑฐ</button>` :
|
| 2447 |
`<button class="feature-btn" data-path="${pdf.path}">๋ฉ์ธ์ ํ์</button>`}
|
|
|
|
| 2696 |
</html>
|
| 2697 |
"""
|
| 2698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2699 |
if __name__ == "__main__":
|
| 2700 |
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
|