import asyncio import logging import os from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse from routers import health, embeddings, classification, daily_report, rag, chatbot from db.vector_store import vector_store from db.metadata_store import metadata_store from services.ollama_client import ollama_client from middlewares.auth import verify_token logger = logging.getLogger(__name__) PUBLIC_PATHS = {"/", "/api/ai/health", "/api/ai/models"} class AuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): if request.method == "OPTIONS" or request.url.path in PUBLIC_PATHS: return await call_next(request) try: request.state.user = await verify_token(request) except Exception as e: return JSONResponse(status_code=401, content={"detail": str(e.detail) if hasattr(e, "detail") else "인증 실패"}) return await call_next(request) async def _periodic_sync(): """30분마다 전체 이슈 재동기화 (안전망)""" await asyncio.sleep(60) # 시작 후 1분 대기 (초기화 완료 보장) while True: try: from services.embedding_service import sync_all_issues result = await sync_all_issues() logger.info(f"Periodic sync completed: {result}") except asyncio.CancelledError: logger.info("Periodic sync task cancelled") return except Exception as e: logger.warning(f"Periodic sync failed: {e}") await asyncio.sleep(1800) # 30분 @asynccontextmanager async def lifespan(app: FastAPI): vector_store.initialize() metadata_store.initialize() sync_task = asyncio.create_task(_periodic_sync()) yield sync_task.cancel() await ollama_client.close() app = FastAPI( title="TK AI Service", description="AI 서비스 (유사 검색, 분류, 보고서)", version="1.0.0", lifespan=lifespan, ) ALLOWED_ORIGINS = [ "https://tkfb.technicalkorea.net", "https://tkreport.technicalkorea.net", "https://tkqc.technicalkorea.net", "https://tkuser.technicalkorea.net", ] if os.getenv("ENV", "production") == "development": ALLOWED_ORIGINS += ["http://localhost:30080", "http://localhost:30180", "http://localhost:30280"] app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.add_middleware(AuthMiddleware) app.include_router(health.router, prefix="/api/ai") app.include_router(embeddings.router, prefix="/api/ai") app.include_router(classification.router, prefix="/api/ai") app.include_router(daily_report.router, prefix="/api/ai") app.include_router(rag.router, prefix="/api/ai") app.include_router(chatbot.router, prefix="/api/ai") @app.get("/") async def root(): return {"message": "TK AI Service", "version": "1.0.0"}