AkJeond's picture
fix: Dockerfile ๋ฐ FastAPI CORS ์„ค์ • ๊ฐœ์„ 
0546432
"""
SmartEyeSsen Backend - FastAPI Main Application
================================================
FastAPI ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ ๋ผ์šฐํ„ฐ ์„ค์ •
์ฃผ์š” ๊ธฐ๋Šฅ:
- FastAPI ์•ฑ ์ดˆ๊ธฐํ™”
- CORS ์„ค์ •
- ๋ผ์šฐํ„ฐ ๋“ฑ๋ก
- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”
- API ๋ฌธ์„œํ™”
"""
import os
from pathlib import Path
from dotenv import load_dotenv
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlalchemy import text
from sqlalchemy.orm import Session
from .database import engine, get_db, init_db, test_connection
from . import models
from .routers import analysis, downloads, pages, projects
from .services.model_registry import model_registry
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ํ™˜๊ฒฝ ์„ค์ • (development | production)
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
UPLOAD_DIR = Path(os.getenv("UPLOAD_DIR", "uploads")).resolve()
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
# ============================================================================
# FastAPI ์•ฑ ์ดˆ๊ธฐํ™”
# ============================================================================
app = FastAPI(
title="SmartEyeSsen API",
description="""
## SmartEyeSsen Backend API
์‹œ๊ฐ์žฅ์•  ํ•™์ƒ์„ ์œ„ํ•œ AI ๊ธฐ๋ฐ˜ ํ•™์Šต ์ž๋ฃŒ ๋ถ„์„ ์‹œ์Šคํ…œ
### ์ฃผ์š” ๊ธฐ๋Šฅ
* ๐Ÿ“„ **๋‹ค์ค‘ ํŽ˜์ด์ง€ ๋ฌธ์„œ ์ฒ˜๋ฆฌ**: Worksheet ๋ฐ Document ์œ ํ˜• ์ง€์›
* ๐Ÿค– **AI ๋ ˆ์ด์•„์›ƒ ๋ถ„์„**: DocLayout-YOLO ๊ธฐ๋ฐ˜ ๋ ˆ์ด์•„์›ƒ ๊ฐ์ง€
* ๐Ÿ” **OCR ํ…์ŠคํŠธ ์ถ”์ถœ**: Tesseract OCR ๊ธฐ๋ฐ˜ ํ…์ŠคํŠธ ์ธ์‹
* โœ๏ธ **ํ…์ŠคํŠธ ํŽธ์ง‘ ๋ฐ ๋ฒ„์ „ ๊ด€๋ฆฌ**: TinyMCE ํŽธ์ง‘๊ธฐ ์ง€์›
* ๐Ÿ–ผ๏ธ **AI ์„ค๋ช… ์ƒ์„ฑ**: GPT-4-turbo ๊ธฐ๋ฐ˜ figure/table/flowchart ์„ค๋ช…
* ๐Ÿ“Š **๋ฌธ์ œ ๊ธฐ๋ฐ˜ ์ •๋ ฌ**: Worksheet ์ „์šฉ ๋ฌธ์ œ ๋ฒˆํ˜ธ ๊ธฐ๋ฐ˜ ์ •๋ ฌ
* ๐Ÿ“ **์ขŒํ‘œ ๊ธฐ๋ฐ˜ ์ •๋ ฌ**: Document ์ „์šฉ ์ขŒํ‘œ ๊ธฐ๋ฐ˜ ์ •๋ ฌ
* ๐Ÿ“ฅ **ํ†ตํ•ฉ ๋ฌธ์„œ ๋‹ค์šด๋กœ๋“œ**: DOCX ํ˜•์‹ ์ง€์›
### ๊ธฐ์ˆ  ์Šคํƒ
* **Backend**: FastAPI + SQLAlchemy
* **Database**: MySQL 8.0
* **AI Models**: DocLayout-YOLO, Tesseract OCR, GPT-4-turbo
* **Document**: python-docx
""",
version="1.0.1",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
)
# ============================================================================
# CORS ์„ค์ • (ํ™˜๊ฒฝ๋ณ„ ๋ถ„๋ฆฌ)
# ============================================================================
if ENVIRONMENT == "production":
# ํ”„๋กœ๋•์…˜: Vercel + HF Spaces ๋ฐฐํฌ ํ™˜๊ฒฝ
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ CORS_ORIGINS ์„ค์ • ๊ฐ€๋Šฅ (์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„)
# ์˜ˆ: CORS_ORIGINS="https://yourapp.vercel.app,https://www.yourapp.com"
CORS_ORIGINS_ENV = os.getenv("CORS_ORIGINS", "")
if CORS_ORIGINS_ENV:
CORS_ORIGINS = CORS_ORIGINS_ENV.split(",")
else:
# ๊ธฐ๋ณธ๊ฐ’: ๋ชจ๋“  ๋„๋ฉ”์ธ ํ—ˆ์šฉ (์ดˆ๊ธฐ ๋ฐฐํฌ/ํ…Œ์ŠคํŠธ์šฉ)
# ๋ณด์•ˆ ๊ฐ•ํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉด ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ํŠน์ • ๋„๋ฉ”์ธ ์ง€์ •
CORS_ORIGINS = ["*"]
CORS_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"]
CORS_HEADERS = ["Content-Type", "Authorization", "X-Requested-With"]
else:
# ๊ฐœ๋ฐœ: ์œ ์—ฐํ•œ CORS
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "http://localhost:5173,http://localhost:3000,http://localhost:8080,http://127.0.0.1:5173").split(",")
CORS_METHODS = ["*"]
CORS_HEADERS = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=CORS_ORIGINS, # ํ—ˆ์šฉํ•  ์ถœ์ฒ˜
allow_credentials=True,
allow_methods=CORS_METHODS, # ํ™˜๊ฒฝ๋ณ„ ๋ฉ”์†Œ๋“œ ์ œํ•œ
allow_headers=CORS_HEADERS, # ํ™˜๊ฒฝ๋ณ„ ํ—ค๋” ์ œํ•œ
)
# ์—…๋กœ๋“œ ํŒŒ์ผ ์ •์  ์„œ๋น™ (ํ”„๋ก ํŠธ์—”๋“œ ์ธ๋„ค์ผ ํ‘œ์‹œ ๋“ฑ)
app.mount(
"/uploads",
StaticFiles(directory=str(UPLOAD_DIR)),
name="uploads",
)
# ============================================================================
# ์‹œ์ž‘ ์ด๋ฒคํŠธ
# ============================================================================
@app.on_event("startup")
async def startup_event():
"""
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์‹คํ–‰
- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
- ํ…Œ์ด๋ธ” ์ƒ์„ฑ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ)
"""
print("=" * 60)
print("๐Ÿš€ SmartEyeSsen Backend Starting...")
print("=" * 60)
# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
if test_connection():
print("โœ… Database connection successful")
else:
print("โŒ Database connection failed")
print("โš ๏ธ Please check your database configuration")
# ํ…Œ์ด๋ธ” ์ƒ์„ฑ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ)
if os.getenv("ENVIRONMENT", "development") == "development":
try:
init_db()
print("โœ… Database tables initialized")
except Exception as e:
print(f"โš ๏ธ Table initialization warning: {e}")
preload_env = os.getenv("MODEL_PRELOAD", "SmartEyeSsen")
preload_targets = [
name.strip()
for name in preload_env.split(",")
if name.strip()
]
if preload_targets:
try:
model_registry.preload(preload_targets)
print(f"๐Ÿง  Preloaded models: {', '.join(preload_targets)}")
except Exception as e:
print(f"โš ๏ธ Model preload failed: {e}")
print("=" * 60)
print("โœ… SmartEyeSsen Backend Ready!")
print(f"๐Ÿ“– API Docs: http://localhost:{os.getenv('API_PORT', 8000)}/docs")
print("=" * 60)
@app.on_event("shutdown")
async def shutdown_event():
"""์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ ์‹œ ์‹คํ–‰"""
print("\n" + "=" * 60)
print("๐Ÿ‘‹ SmartEyeSsen Backend Shutting down...")
print("=" * 60)
# ============================================================================
# ๋ฃจํŠธ ์—”๋“œํฌ์ธํŠธ
# ============================================================================
@app.get("/", tags=["Root"])
async def root():
"""
๋ฃจํŠธ ์—”๋“œํฌ์ธํŠธ
์„œ๋ฒ„ ์ƒํƒœ ๋ฐ ๊ธฐ๋ณธ ์ •๋ณด ๋ฐ˜ํ™˜
"""
return {
"message": "Welcome to SmartEyeSsen API",
"version": "1.0.1",
"status": "running",
"docs": "/docs",
"redoc": "/redoc"
}
@app.get("/health", tags=["Root"])
async def health_check(db: Session = Depends(get_db)):
"""
ํ—ฌ์Šค ์ฒดํฌ ์—”๋“œํฌ์ธํŠธ
์„œ๋ฒ„ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ ํ™•์ธ
"""
try:
# ๊ฐ„๋‹จํ•œ ์ฟผ๋ฆฌ๋กœ DB ์—ฐ๊ฒฐ ํ™•์ธ
db.execute(text("SELECT 1"))
db_status = "connected"
except Exception as e:
db_status = f"error: {str(e)}"
return {
"status": "healthy",
"database": db_status,
"api_version": "1.0.0"
}
# ============================================================================
# ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ
# ============================================================================
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
"""HTTP ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ"""
return JSONResponse(
status_code=exc.status_code,
content={
"error": exc.detail,
"status_code": exc.status_code
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request, exc):
"""์ผ๋ฐ˜ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ"""
return JSONResponse(
status_code=500,
content={
"error": "Internal Server Error",
"detail": str(exc),
"status_code": 500
}
)
# =========================================================================
# ๋ผ์šฐํ„ฐ ๋“ฑ๋ก
# =========================================================================
app.include_router(projects.router)
app.include_router(pages.router)
app.include_router(analysis.router)
app.include_router(downloads.router)
# ============================================================================
# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ (์ง์ ‘ ์‹คํ–‰ ์‹œ)
# ============================================================================
if __name__ == "__main__":
import uvicorn
HOST = os.getenv("API_HOST", "0.0.0.0")
PORT = int(os.getenv("API_PORT", 8000))
RELOAD = os.getenv("API_RELOAD", "True").lower() == "true"
uvicorn.run(
"main:app",
host=HOST,
port=PORT,
reload=RELOAD,
log_level="info"
)