main.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect, Query
  2. from fastapi.staticfiles import StaticFiles
  3. from fastapi.middleware.cors import CORSMiddleware
  4. from fastapi.exceptions import RequestValidationError
  5. from fastapi.responses import JSONResponse
  6. import traceback
  7. import os
  8. import auth_utils
  9. import session_utils
  10. from services.global_manager import global_manager
  11. import locales
  12. import config
  13. from routers import auth, orders, catalog, portfolio, files, chat, blog, admin
  14. app = FastAPI(title="Radionica 3D API")
  15. # Configure CORS
  16. origins = [
  17. "http://localhost:5173",
  18. "http://127.0.0.1:5173",
  19. "http://localhost:5000",
  20. "https://radionica3d.me",
  21. ]
  22. extra_origins = os.getenv("CORS_ORIGINS")
  23. if extra_origins:
  24. origins.extend(extra_origins.split(","))
  25. app.add_middleware(
  26. CORSMiddleware,
  27. allow_origins=origins,
  28. allow_credentials=True,
  29. allow_methods=["*"],
  30. allow_headers=["*"],
  31. )
  32. @app.exception_handler(RequestValidationError)
  33. async def validation_exception_handler(request: Request, exc: RequestValidationError):
  34. lang = request.query_params.get("lang", "en")
  35. errors = []
  36. for error in exc.errors():
  37. error_type = error.get("type", "unknown")
  38. ctx = error.get("ctx", {})
  39. translated_msg = locales.translate_error(error_type, lang, **ctx)
  40. loc = ".".join(str(l) for l in error.get("loc", [])[1:])
  41. errors.append({
  42. "loc": error.get("loc"),
  43. "msg": f"{loc}: {translated_msg}" if loc else translated_msg,
  44. "type": error_type
  45. })
  46. return JSONResponse(status_code=422, content={"detail": errors})
  47. @app.exception_handler(Exception)
  48. async def all_exception_handler(request: Request, exc: Exception):
  49. print(f"ERROR: {exc}")
  50. traceback.print_exc()
  51. if config.DEBUG:
  52. return JSONResponse(
  53. status_code=500,
  54. content={"detail": str(exc), "traceback": traceback.format_exc()}
  55. )
  56. return JSONResponse(status_code=500, content={"detail": "Internal server error"})
  57. # Add custom exception logging or other middleware here if needed
  58. # Include Routers
  59. app.include_router(auth.router)
  60. app.include_router(orders.router)
  61. app.include_router(catalog.router)
  62. app.include_router(portfolio.router)
  63. app.include_router(files.router)
  64. app.include_router(chat.router)
  65. app.include_router(blog.router)
  66. app.include_router(admin.router)
  67. # WebSocket Global Handler (Centralized to handle various proxy prefixes)
  68. @app.websocket("/ws/auth/ws/global")
  69. @app.websocket("/auth/ws/global")
  70. async def ws_global(websocket: WebSocket, token: str = Query(...)):
  71. print(f"DEBUG: WS Connection attempt with token: {token[:10]}...")
  72. payload = auth_utils.decode_token(token)
  73. if not payload:
  74. print("DEBUG: WS Auth failed: Invalid token")
  75. await websocket.close(code=4001)
  76. return
  77. user_id = payload.get("id")
  78. role = payload.get("role")
  79. if not user_id:
  80. print("DEBUG: WS Auth failed: No user ID in payload")
  81. await websocket.close(code=4001)
  82. return
  83. print(f"DEBUG: WS Connected: user_id={user_id}, role={role}")
  84. await global_manager.connect(websocket, user_id, role)
  85. session_utils.track_user_ping(user_id)
  86. # Send initial unread count
  87. if role != 'admin':
  88. await global_manager.notify_user(user_id)
  89. else:
  90. await global_manager.notify_admins()
  91. try:
  92. while True:
  93. data = await websocket.receive_text()
  94. if data == "ping":
  95. session_utils.track_user_ping(user_id)
  96. except WebSocketDisconnect:
  97. print(f"DEBUG: WS Disconnected: user_id={user_id}")
  98. global_manager.disconnect(websocket, user_id)
  99. except Exception as e:
  100. print(f"DEBUG: WS Error: {e}")
  101. global_manager.disconnect(websocket, user_id)
  102. # Mount Static Files
  103. if not os.path.exists("uploads"):
  104. os.makedirs("uploads")
  105. # Mount static files for uploads and previews with caching
  106. app.mount("/uploads", StaticFiles(directory="uploads", html=False), name="uploads")
  107. @app.middleware("http")
  108. async def add_cache_control_header(request, call_next):
  109. response = await call_next(request)
  110. if request.url.path.startswith("/uploads"):
  111. response.headers["Cache-Control"] = "public, max-age=604800, immutable"
  112. return response
  113. if __name__ == "__main__":
  114. import uvicorn
  115. uvicorn.run(app, host="0.0.0.0", port=8000)