| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- import os
- # Fix matplotlib/gunicorn permission issues in server environment
- os.environ['MPLCONFIGDIR'] = os.path.join(os.getcwd(), 'temp')
- from fastapi import FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect, Query
- from fastapi.staticfiles import StaticFiles
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.exceptions import RequestValidationError
- from fastapi.responses import JSONResponse
- import traceback
- import os
- import auth_utils
- import session_utils
- from services.global_manager import global_manager
- from services.chat_manager import manager
- import locales
- import config
- import db
- from routers import auth, orders, catalog, portfolio, files, chat, blog, admin, contact, warehouse
- app = FastAPI(title="Radionica 3D API")
- # Configure CORS
- origins = [
- "http://localhost:5173",
- "http://127.0.0.1:5173",
- "http://localhost:5000",
- "https://radionica3d.me",
- ]
- extra_origins = os.getenv("CORS_ORIGINS")
- if extra_origins:
- origins.extend(extra_origins.split(","))
- app.add_middleware(
- CORSMiddleware,
- allow_origins=origins,
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
- )
- @app.exception_handler(RequestValidationError)
- async def validation_exception_handler(request: Request, exc: RequestValidationError):
- lang = request.query_params.get("lang", "en")
- errors = []
- for error in exc.errors():
- error_type = error.get("type", "unknown")
- ctx = error.get("ctx", {})
- translated_msg = locales.translate_error(error_type, lang, **ctx)
- loc = ".".join(str(l) for l in error.get("loc", [])[1:])
- errors.append({
- "loc": error.get("loc"),
- "msg": f"{loc}: {translated_msg}" if loc else translated_msg,
- "type": error_type
- })
- return JSONResponse(status_code=422, content={"detail": errors})
- @app.exception_handler(Exception)
- async def all_exception_handler(request: Request, exc: Exception):
- print(f"ERROR: {exc}")
- traceback.print_exc()
- if config.DEBUG:
- return JSONResponse(
- status_code=500,
- content={"detail": str(exc), "traceback": traceback.format_exc()}
- )
- return JSONResponse(status_code=500, content={"detail": "Internal server error"})
- # Add custom exception logging or other middleware here if needed
- # Include Routers
- app.include_router(auth.router)
- app.include_router(orders.router)
- app.include_router(catalog.router)
- app.include_router(portfolio.router)
- app.include_router(files.router)
- app.include_router(chat.router)
- app.include_router(blog.router)
- app.include_router(admin.router)
- app.include_router(contact.router)
- app.include_router(warehouse.router)
- # WebSocket Global Handler (Centralized to handle various proxy prefixes)
- @app.websocket("/global")
- async def ws_global(websocket: WebSocket, token: str = Query(...)):
- payload = auth_utils.decode_token(token)
- if not payload:
- await websocket.close(code=4001)
- return
- user_id = payload.get("id")
- role = payload.get("role")
- if not user_id:
- await websocket.close(code=4001)
- return
-
- await global_manager.connect(websocket, user_id, role)
- session_utils.track_user_ping(user_id)
-
- # Send initial unread count
- if role != 'admin':
- await global_manager.notify_user(user_id)
- else:
- await global_manager.notify_admins()
-
- try:
- while True:
- data = await websocket.receive_text()
- if data == "ping":
- session_utils.track_user_ping(user_id)
- except WebSocketDisconnect:
- global_manager.disconnect(websocket, user_id)
- except Exception as e:
- global_manager.disconnect(websocket, user_id)
- @app.websocket("/chat")
- async def ws_chat(websocket: WebSocket, token: str = Query(...), order_id: int = Query(...)):
- payload = auth_utils.decode_token(token)
- if not payload:
- await websocket.close(code=4001)
- return
- role = payload.get("role")
- user_id = payload.get("id")
- if role != 'admin':
- user_info = db.execute_query("SELECT can_chat FROM users WHERE id = %s", (user_id,))
- if not user_info or not user_info[0]['can_chat']:
- await websocket.close(code=4003)
- return
- order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
- if not order:
- await websocket.close(code=4004)
- return
- if role != 'admin' and order[0]['user_id'] != user_id:
- await websocket.close(code=4003)
- return
-
- await manager.connect(websocket, order_id, role)
- try:
- while True:
- data = await websocket.receive_text()
- if data == "typing":
- await manager.broadcast_to_order(order_id, {"type": "typing", "is_admin": role == 'admin'})
- elif data == "stop_typing":
- await manager.broadcast_to_order(order_id, {"type": "stop_typing", "is_admin": role == 'admin'})
- elif data == "read":
- if role == 'admin':
- db.execute_commit("UPDATE order_messages SET is_read = TRUE WHERE order_id = %s AND is_from_admin = FALSE AND is_read = FALSE", (order_id,))
- await global_manager.notify_admins()
- await global_manager.notify_order_read(order_id)
- else:
- db.execute_commit("UPDATE order_messages SET is_read = TRUE WHERE order_id = %s AND is_from_admin = TRUE AND is_read = FALSE", (order_id,))
- await global_manager.notify_user(user_id)
- except WebSocketDisconnect:
- manager.disconnect(websocket, order_id)
- except Exception as e:
- manager.disconnect(websocket, order_id)
- # Mount Static Files
- for sub in ["", "previews", "invoices", "reports"]:
- path = os.path.join("uploads", sub)
- if not os.path.exists(path):
- os.makedirs(path, exist_ok=True)
- # Mount static files for uploads and previews with caching
- app.mount("/uploads", StaticFiles(directory="uploads", html=False), name="uploads")
- @app.middleware("http")
- async def add_cache_control_header(request, call_next):
- response = await call_next(request)
- if request.url.path.startswith("/uploads"):
- response.headers["Cache-Control"] = "public, max-age=604800, immutable"
- return response
- if __name__ == "__main__":
- import uvicorn
- uvicorn.run(app, host="127.0.0.1", port=8000)
|