import os import sys import asyncio import pymysql from dotenv import load_dotenv from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from analyze import analyze_file_content from crawler_service import run_crawler_service, crawl_stop_event from schemas import AuthRequest, InquiryReplyRequest from inquiry_service import InquiryService from project_service import ProjectService from analysis_service import AnalysisService # --- 환경 설정 --- load_dotenv() os.environ["PYTHONIOENCODING"] = "utf-8" TESSDATA_PREFIX = os.getenv("TESSDATA_PREFIX", r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata") os.environ["TESSDATA_PREFIX"] = TESSDATA_PREFIX app = FastAPI(title="Project Master Overseas API") templates = Jinja2Templates(directory="templates") # 정적 파일 마운트 app.mount("/style", StaticFiles(directory="style"), name="style") app.mount("/js", StaticFiles(directory="js"), name="js") app.mount("/sample_files", StaticFiles(directory="sample"), name="sample_files") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["*"], allow_headers=["*"], ) # --- 유틸리티 함수 --- def get_db_connection(): """MySQL 데이터베이스 연결을 반환""" return pymysql.connect( host=os.getenv('DB_HOST', 'localhost'), port=int(os.getenv('DB_PORT', 3306)), user=os.getenv('DB_USER', 'root'), password=os.getenv('DB_PASSWORD', '45278434'), database=os.getenv('DB_NAME', 'PM_proto'), charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) async def run_in_threadpool(func, *args): """동기 함수를 비차단 방식으로 실행""" loop = asyncio.get_event_loop() return await loop.run_in_executor(None, func, *args) # --- HTML 라우팅 --- @app.get("/") async def root(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.get("/dashboard") async def get_dashboard(request: Request): return templates.TemplateResponse("dashboard.html", {"request": request}) @app.get("/mailTest") async def get_mail_test(request: Request): return templates.TemplateResponse("mailTest.html", {"request": request}) @app.get("/inquiries") async def get_inquiries_page(request: Request): return templates.TemplateResponse("inquiries.html", {"request": request}) @app.get("/analysis") async def get_analysis_page(request: Request): return templates.TemplateResponse("analysis.html", {"request": request}) # --- 문의사항 API --- @app.get("/api/inquiries") async def get_inquiries(pm_type: str = None, category: str = None, status: str = None, keyword: str = None): try: with get_db_connection() as conn: with conn.cursor() as cursor: return InquiryService.get_inquiries_logic(cursor, pm_type, category, status, keyword) except Exception as e: return {"error": str(e)} @app.get("/api/inquiries/{id}") async def get_inquiry_detail(id: int): try: with get_db_connection() as conn: with conn.cursor() as cursor: return InquiryService.get_inquiry_detail_logic(cursor, id) except Exception as e: return {"error": str(e)} @app.post("/api/inquiries/{id}/reply") async def update_inquiry_reply(id: int, req: InquiryReplyRequest): try: with get_db_connection() as conn: with conn.cursor() as cursor: return InquiryService.update_inquiry_reply_logic(cursor, conn, id, req) except Exception as e: return {"error": str(e)} @app.delete("/api/inquiries/{id}/reply") async def delete_inquiry_reply(id: int): try: with get_db_connection() as conn: with conn.cursor() as cursor: return InquiryService.delete_inquiry_reply_logic(cursor, conn, id) except Exception as e: return {"error": str(e)} # --- 프로젝트 및 히스토리 API --- @app.get("/available-dates") async def get_available_dates(): try: with get_db_connection() as conn: with conn.cursor() as cursor: return ProjectService.get_available_dates_logic(cursor) except Exception as e: return {"error": str(e)} @app.get("/project-data") async def get_project_data(date: str = None): try: with get_db_connection() as conn: with conn.cursor() as cursor: return ProjectService.get_project_data_logic(cursor, date) except Exception as e: return {"error": str(e)} # --- 분석 API (AnalysisService 연동) --- @app.get("/project-activity") async def get_project_activity(date: str = None): try: with get_db_connection() as conn: with conn.cursor() as cursor: return AnalysisService.get_project_activity_logic(cursor, date) except Exception as e: return {"error": str(e)} @app.get("/api/analysis/p-war") async def get_p_war_analysis(): try: with get_db_connection() as conn: with conn.cursor() as cursor: return AnalysisService.get_p_zsr_analysis_logic(cursor) except Exception as e: return {"error": str(e)} # --- 수집 및 동기화 API --- @app.post("/auth/crawl") async def auth_crawl(req: AuthRequest): if req.user_id == os.getenv("PM_USER_ID") and req.password == os.getenv("PM_PASSWORD"): return {"success": True} return {"success": False, "message": "크롤링을 할 수 없습니다."} @app.get("/sync") async def sync_data(): return StreamingResponse(run_crawler_service(), media_type="text_event-stream") @app.get("/stop-sync") async def stop_sync(): crawl_stop_event.set() return {"success": True} # --- 파일 및 첨부파일 API --- @app.get("/attachments") async def get_attachments(): path = "sample" if not os.path.exists(path): os.makedirs(path) return [{"name": f, "size": f"{os.path.getsize(os.path.join(path, f))/1024:.1f} KB"} for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] @app.get("/analyze-file") async def analyze_file(filename: str): return await run_in_threadpool(analyze_file_content, filename) @app.get("/sample.png") async def get_sample_img(): return FileResponse("sample.png")