218 lines
9.1 KiB
Python
218 lines
9.1 KiB
Python
"""
|
|
main.py
|
|
FastAPI 애플리케이션 진입점
|
|
|
|
- 공통 설정 로드 (config_system, config_db)
|
|
- 프론트엔드 자동 빌드 및 서빙 (npm run build)
|
|
- 정적 파일 서빙 (프론트엔드 빌드 결과)
|
|
- DB 풀 초기화/종료
|
|
- 라우터 등록 (페이지별 API)
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from B03_FileInput.B03_FileInput_Router import router as b03_file_input_router
|
|
from B04_wf1_Surface.B04_wf1_Surface_Router import router as b04_surface_router
|
|
from B05_wf2_Route.B05_wf2_Route_Router import router as b05_route_router
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Router import router as b06_section_router
|
|
from config.config_db import close_db_pool, init_db_pool
|
|
|
|
# 설정 import
|
|
from config.config_system import (
|
|
CORS_ORIGINS,
|
|
DEBUG,
|
|
ENVIRONMENT,
|
|
LOG_LEVEL,
|
|
SERVER_HOST,
|
|
SERVER_PORT,
|
|
STATIC_DIR,
|
|
STATIC_URL,
|
|
)
|
|
|
|
# 로깅 설정
|
|
logging.basicConfig(level=getattr(logging, LOG_LEVEL))
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 프론트엔드 빌드 및 서빙 함수
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def build_frontend() -> bool:
|
|
"""프론트엔드 빌드 (npm run build from config/)"""
|
|
root_dir = Path(__file__).resolve().parent
|
|
config_dir = root_dir / "config"
|
|
|
|
if not config_dir.exists():
|
|
logger.warning(f"⚠ config 디렉토리 없음: {config_dir}")
|
|
return False
|
|
|
|
logger.info("[Frontend] npm run build 시작...")
|
|
try:
|
|
result = subprocess.run(
|
|
"npm run build",
|
|
shell=True,
|
|
cwd=str(config_dir),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
timeout=300,
|
|
)
|
|
if result.returncode == 0:
|
|
logger.info("✓ 프론트엔드 빌드 완료")
|
|
return True
|
|
else:
|
|
logger.error(f"✗ 프론트엔드 빌드 실패:\n{result.stderr}")
|
|
return False
|
|
except subprocess.TimeoutExpired:
|
|
logger.error("✗ 프론트엔드 빌드 타임아웃 (300초)")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"✗ 프론트엔드 빌드 중 예외: {e}")
|
|
return False
|
|
|
|
|
|
def serve_frontend_dev() -> None:
|
|
"""프론트엔드 개발 서버 실행 (npm run dev from config/) — 선택 사항"""
|
|
root_dir = Path(__file__).resolve().parent
|
|
config_dir = root_dir / "config"
|
|
|
|
if not config_dir.exists():
|
|
logger.warning(f"⚠ config 디렉토리 없음, 개발 서버 스킵: {config_dir}")
|
|
return
|
|
|
|
if ENVIRONMENT == "production":
|
|
logger.info("[Frontend] 프로덕션 환경: 개발 서버 실행 스킵")
|
|
return
|
|
|
|
logger.info("[Frontend] npm run dev 시작 (백그라운드)...")
|
|
try:
|
|
subprocess.Popen(
|
|
"npm run dev",
|
|
shell=True,
|
|
cwd=str(config_dir),
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
logger.info("✓ 프론트엔드 개발 서버 백그라운드 실행")
|
|
except Exception as e:
|
|
logger.warning(f"⚠ 프론트엔드 개발 서버 실행 실패: {e}")
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 라이프사이클: 앱 시작/종료
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""FastAPI 라이프사이클 관리"""
|
|
# 시작
|
|
logger.info(f"[{ENVIRONMENT}] 앱 시작 중...")
|
|
|
|
# 프론트엔드 빌드 (필수)
|
|
build_frontend()
|
|
|
|
# 개발 환경에서 개발 서버 실행 (선택사항)
|
|
if ENVIRONMENT == "development":
|
|
serve_frontend_dev()
|
|
|
|
# DB 풀 초기화
|
|
await init_db_pool()
|
|
logger.info("✓ DB 풀 초기화 완료")
|
|
|
|
yield
|
|
|
|
# 종료
|
|
logger.info("앱 종료 중...")
|
|
await close_db_pool()
|
|
logger.info("✓ DB 풀 종료 완료")
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# FastAPI 앱 생성
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
app = FastAPI(
|
|
title="임도 설계 및 견적 자동화",
|
|
description="Forest Road Design & Estimation API",
|
|
version="0.1.0",
|
|
debug=DEBUG,
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# CORS 미들웨어
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 정적 파일 서빙 (프론트엔드)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
if os.path.isdir(STATIC_DIR):
|
|
app.mount(STATIC_URL, StaticFiles(directory=STATIC_DIR), name="static")
|
|
logger.info(f"✓ 정적 파일 서빙: {STATIC_URL} → {STATIC_DIR}")
|
|
else:
|
|
logger.warning(f"⚠ 정적 파일 디렉토리 없음: {STATIC_DIR}")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 기본 엔드포인트
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""루트 엔드포인트 — 프론트엔드 진입점 리다이렉트"""
|
|
return {
|
|
"message": "임도 설계 및 견적 자동화 API",
|
|
"version": "0.1.0",
|
|
"frontend": f"http://{SERVER_HOST}:{SERVER_PORT}{STATIC_URL}/",
|
|
}
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def health():
|
|
"""헬스 체크"""
|
|
return {"status": "ok", "environment": ENVIRONMENT}
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 라우터 등록 (pages → API)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 예: from A01_Home.A01_Home_Api_Fetch import router as a01_router
|
|
# app.include_router(a01_router, prefix="/api/a01", tags=["A01_Home"])
|
|
#
|
|
# 나중에 각 페이지별 라우터가 구현되면 여기에 등록.
|
|
app.include_router(b03_file_input_router)
|
|
app.include_router(b04_surface_router)
|
|
app.include_router(b05_route_router)
|
|
app.include_router(b06_section_router)
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# 앱 실행
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
logger.info(f"Starting server on {SERVER_HOST}:{SERVER_PORT}")
|
|
uvicorn.run(
|
|
"main:app",
|
|
host=SERVER_HOST,
|
|
port=SERVER_PORT,
|
|
reload=DEBUG,
|
|
)
|