# app/main.py - FastAPI Application Entry Point from contextlib import asynccontextmanager from datetime import datetime from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, HTMLResponse, Response, FileResponse from fastapi.staticfiles import StaticFiles import time import os import sys # Force UTF-8 for Windows consoles and file system os.environ["PYTHONUTF8"] = "1" # if sys.platform == "win32": # import codecs # sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) # sys.stderr = codecs.getwriter("utf-8")(sys.stderr.detach()) from app.config import settings from app.agents.orchestrator import orchestrator from app.api.routes import api_router, enforcement_router, guvi_router from app.utils.logger import setup_logging setup_logging() BANNER = """ ======================================================================== SENTINEL - Scam Honeypot System ======================================================================== """ @asynccontextmanager async def lifespan(app: FastAPI): """Initialize and cleanup application resources.""" print(BANNER) print("Starting API...") from app.database.db import init_db, close_db await init_db() await orchestrator.initialize() yield print("Shutting down...") await orchestrator.shutdown() await close_db() app = FastAPI( title="Scam Honeypot API", description="AI-powered honeypot for scam detection and intelligence extraction", version=settings.VERSION, docs_url="/docs", redoc_url="/redoc", lifespan=lifespan ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # from app.middleware.rate_limiter import RateLimitMiddleware # app.add_middleware(RateLimitMiddleware) # Consolidated Web Folder Mapping WEB_DIR = "web" app.mount("/static", StaticFiles(directory=WEB_DIR), name="static") app.mount("/docs", StaticFiles(directory="docs"), name="docs") @app.middleware("http") async def add_process_time_header(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(round(process_time * 1000, 2)) return response @app.get("/favicon.ico", include_in_schema=False) async def favicon(): return Response(content="Honeypot", media_type="image/x-icon") @app.get("/", response_class=FileResponse, tags=["Info"]) async def root(request: Request): """Landing page serving from consolidated folder.""" return FileResponse(f"{WEB_DIR}/index.html") # API Routers app.include_router(api_router) app.include_router(enforcement_router) app.include_router(guvi_router) print("============================================================") print(f"DEBUG: STARTING APP FROM: {__file__}") print("============================================================") from app.decoys.fake_endpoints import router as decoy_router app.include_router(decoy_router) from app.api.feeds import router as feeds_router app.include_router(feeds_router, prefix="/api/v1/intelligence") # Mount web directory to root as a fallback for relative assets (style.css, app.js) app.mount("/", StaticFiles(directory=WEB_DIR), name="web_root") from fastapi.exceptions import RequestValidationError from app.utils.logger import AgentLogger api_logger = AgentLogger("api") @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): # DEBUG: Log validation errors for GUVI endpoint body = None try: body = await request.body() body_str = body.decode('utf-8')[:500] if body else "EMPTY" except: body_str = "UNREADABLE" api_logger.error(f"[VALIDATION ERROR] Path: {request.url.path}") api_logger.error(f"[VALIDATION ERROR] Body Preview: {body_str}") api_logger.error(f"[VALIDATION ERROR] Details: {str(exc.errors())}") return JSONResponse(status_code=422, content={"status": "error", "message": "Validation Error", "detail": exc.errors()}) @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): import traceback traceback.print_exc() # Log to console/file return JSONResponse(status_code=500, content={"status": "error", "message": str(exc)}) if __name__ == "__main__": import uvicorn # Hugging Face Spaces defaults to 7860 port = int(os.getenv("PORT", 7860)) # Disable reload in production for better performance and stability uvicorn.run("app.main:app", host="0.0.0.0", port=port, reload=False)