from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.config import settings from app.core.exceptions import register_exception_handlers from app.core.middleware import TierMiddleware, QuotaMiddleware, RateLimitMiddleware from app.core.csrf import CSRFMiddleware import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) try: import sentry_sdk from sentry_sdk.integrations.fastapi import FastApiIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration sentry_sdk.init( dsn=settings.SENTRY_DSN, traces_sample_rate=0.1, environment="production" if not settings.DEBUG else "development", integrations=[ FastApiIntegration(), SqlalchemyIntegration(), ], ) logger.info("Sentry initialized") except (ImportError, Exception) as e: logger.info(f"Sentry not configured: {e}") app = FastAPI( title=settings.APP_NAME, version="1.0.0", docs_url="/docs", redoc_url="/redoc", debug=settings.DEBUG, ) # ============================================================================= # CORS Configuration - Security Hardened # ============================================================================= # Only allow specific origins (frontend URLs) # Only allow specific HTTP methods (no TRACE, CONNECT, etc.) # Only allow specific headers (no arbitrary headers) # ============================================================================= # Define allowed origins from environment/config # In production, this should be your actual frontend domain(s) ALLOWED_ORIGINS = [ settings.FRONTEND_URL, "http://localhost:3000", # Legacy frontend "http://localhost:5173", # Vite dev server "http://localhost:5174", # User workspace dev server "https://trade.yuzhiran.com", # Production domain "https://trade.yuzhiran.com/app", "https://trade.yuzhiran.com/admin", "https://trade.yuzhiran.com/workspace", ] # Allowed HTTP methods (explicitly listed for security) ALLOWED_METHODS = [ "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", ] # Allowed headers (explicitly listed) ALLOWED_HEADERS = [ "Authorization", "Content-Type", "X-CSRF-Token", "X-Requested-With", "Accept", "Origin", ] app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=ALLOWED_METHODS, allow_headers=ALLOWED_HEADERS, max_age=600, # Preflight cache duration expose_headers=[ "X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset", "X-RateLimit-Name", "X-CSRF-Token", ], ) # ============================================================================= # Security Middleware Stack # ============================================================================= # Order matters - CSRF should come after CORS but before other middleware # ============================================================================= app.add_middleware(CSRFMiddleware) app.add_middleware(RateLimitMiddleware) app.add_middleware(QuotaMiddleware) app.add_middleware(TierMiddleware) register_exception_handlers(app) @app.on_event("startup") async def load_ai_providers_from_db(): try: from app.database import get_db from app.ai.router import get_ai_router async for db in get_db(): router = get_ai_router() count = await router.reload_from_db(db) if count == 0: seeded = await router.seed_from_env(db) if seeded: await router.reload_from_db(db) break except Exception as e: logger.warning(f"AI provider DB load failed (tables may not exist yet): {e}") @app.get("/health") async def health(): return {"status": "ok", "app": settings.APP_NAME, "version": "1.0.0"} from app.api.v1 import auth, marketing, translate, customer, quotation, whatsapp, product, exchange, push, admin, analytics, teams, onboarding, notification, feedback, payment, interaction, silent_pattern, training, followup, ai_assistant, discovery, discovery_record, certification, invoice, usage, referral, admin_search, search, admin_ai, credits, admin_credits, agent app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"]) app.include_router(marketing.router, prefix="/api/v1/marketing", tags=["marketing"]) app.include_router(translate.router, prefix="/api/v1/translate", tags=["translate"]) app.include_router(translate.public_router, prefix="/api/v1/translate/public", tags=["translate-public"]) app.include_router(customer.router, prefix="/api/v1/customers", tags=["customers"]) app.include_router(quotation.router, prefix="/api/v1/quotations", tags=["quotations"]) app.include_router(whatsapp.router, prefix="/api/v1/whatsapp", tags=["whatsapp"]) app.include_router(product.router, prefix="/api/v1/products", tags=["products"]) app.include_router(exchange.router, prefix="/api/v1/exchange", tags=["exchange"]) app.include_router(push.router, prefix="/api/v1/push", tags=["push"]) app.include_router(admin.router, prefix="/api/v1/admin", tags=["admin"]) app.include_router(analytics.router, prefix="/api/v1/analytics", tags=["analytics"]) app.include_router(teams.router, prefix="/api/v1/teams", tags=["teams"]) app.include_router(onboarding.router, prefix="/api/v1/onboarding", tags=["onboarding"]) app.include_router(notification.router, prefix="/api/v1/notifications", tags=["notifications"]) app.include_router(feedback.router, prefix="/api/v1/feedback", tags=["feedback"]) app.include_router(payment.router, prefix="/api/v1/payment", tags=["payment"]) app.include_router(interaction.router, prefix="/api/v1/interaction", tags=["interaction"]) app.include_router(silent_pattern.router, prefix="/api/v1/silent-pattern", tags=["silent-pattern"]) app.include_router(training.router, prefix="/api/v1/training", tags=["training"]) app.include_router(followup.router, prefix="/api/v1/followup", tags=["followup"]) app.include_router(ai_assistant.router, prefix="/api/v1/ai", tags=["ai-assistant"]) app.include_router(discovery.router, prefix="/api/v1/discovery", tags=["discovery"]) app.include_router(discovery_record.router, prefix="/api/v1/discovery", tags=["discovery"]) app.include_router(certification.router, prefix="/api/v1/certification", tags=["certification"]) app.include_router(invoice.router, prefix="/api/v1/invoices", tags=["invoices"]) app.include_router(usage.router, prefix="/api/v1/usage", tags=["usage"]) app.include_router(referral.router, prefix="/api/v1/referral", tags=["referral"]) app.include_router(admin_search.router, prefix="/api/v1/admin", tags=["admin"]) app.include_router(admin_ai.router, prefix="/api/v1/admin", tags=["admin"]) app.include_router(admin_credits.router, prefix="/api/v1/admin", tags=["admin"]) app.include_router(credits.router, prefix="/api/v1/credits", tags=["credits"]) app.include_router(search.router, prefix="/api/v1/search", tags=["search"]) app.include_router(agent.router, prefix="/api/v1/agent", tags=["agent"]) if __name__ == "__main__": import uvicorn uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)