"""Main FastAPI application 生產線異常即時反應系統 (Task Reporter) """ import os import json from pathlib import Path from datetime import datetime from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, JSONResponse from app.core.config import get_settings class UTCDateTimeEncoder(json.JSONEncoder): """Custom JSON encoder that formats datetime with 'Z' suffix for UTC""" def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() + 'Z' return super().default(obj) class UTCJSONResponse(JSONResponse): """JSONResponse that uses UTCDateTimeEncoder""" def render(self, content) -> bytes: return json.dumps( content, ensure_ascii=False, allow_nan=False, indent=None, separators=(",", ":"), cls=UTCDateTimeEncoder, ).encode("utf-8") from app.modules.auth import router as auth_router from app.modules.auth.users_router import router as users_router from app.modules.auth.middleware import auth_middleware from app.modules.chat_room import router as chat_room_router from app.modules.realtime import router as realtime_router from app.modules.file_storage import router as file_storage_router from app.modules.report_generation import router as report_generation_router from app.modules.report_generation import health_router as report_health_router # Frontend build directory FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist" settings = get_settings() # Database tables are managed by Alembic migrations # Run: alembic upgrade head # Initialize FastAPI app with custom JSON response for UTC datetime app = FastAPI( title="Task Reporter API", description="Production Line Incident Response System - 生產線異常即時反應系統", version="1.0.0", debug=settings.DEBUG, default_response_class=UTCJSONResponse, ) # CORS middleware - origins configured via CORS_ORIGINS environment variable app.add_middleware( CORSMiddleware, allow_origins=settings.get_cors_origins(), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Authentication middleware (applies to all /api routes except login/logout) app.middleware("http")(auth_middleware) # Include routers app.include_router(auth_router) app.include_router(users_router) app.include_router(chat_room_router) app.include_router(realtime_router) app.include_router(file_storage_router) app.include_router(report_generation_router) app.include_router(report_health_router) @app.on_event("startup") async def startup_event(): """Initialize application on startup""" from app.core.minio_client import initialize_bucket import logging logger = logging.getLogger(__name__) # Initialize MinIO bucket try: if initialize_bucket(): logger.info("MinIO bucket initialized successfully") else: logger.warning("MinIO bucket initialization failed - file uploads may not work") except Exception as e: logger.warning(f"MinIO connection failed: {e} - file uploads will be unavailable") # Check DIFY API Key configuration if not settings.DIFY_API_KEY: logger.warning("DIFY_API_KEY not configured - AI report generation will be unavailable") @app.get("/api/health") async def health_check(): """Health check for monitoring""" return { "status": "healthy", "service": "Task Reporter API", "version": "1.0.0", } # Serve frontend static files (only if build exists) if FRONTEND_DIR.exists(): # Mount static assets (JS, CSS, images) app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="static") @app.get("/{full_path:path}") async def serve_spa(full_path: str): """Serve the React SPA for all non-API routes""" # Try to serve the exact file if it exists file_path = FRONTEND_DIR / full_path if file_path.exists() and file_path.is_file(): return FileResponse(file_path) # Otherwise serve index.html for client-side routing return FileResponse(FRONTEND_DIR / "index.html") if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG, log_level="info" )