feat(socketio): integrate SocketIO service for real-time communication and emit credits change events
This commit is contained in:
@@ -185,6 +185,16 @@ def require_credits(credits_needed: int):
|
||||
user.credits -= credits_needed
|
||||
db.session.commit()
|
||||
|
||||
# Emit credits changed event via SocketIO
|
||||
try:
|
||||
from app.services.socketio_service import socketio_service
|
||||
socketio_service.emit_credits_changed(user.id, user.credits)
|
||||
except Exception as e:
|
||||
# Don't fail the request if SocketIO emission fails
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f"Failed to emit credits_changed event: {e}")
|
||||
|
||||
# Execute the function
|
||||
result = f(*args, **kwargs)
|
||||
|
||||
|
||||
134
app/services/socketio_service.py
Normal file
134
app/services/socketio_service.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""SocketIO service for real-time communication."""
|
||||
|
||||
import logging
|
||||
|
||||
from flask import request
|
||||
from flask_jwt_extended import decode_token
|
||||
from flask_socketio import disconnect, emit, join_room, leave_room
|
||||
|
||||
from app import socketio
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SocketIOService:
|
||||
"""Service for managing SocketIO connections and user rooms."""
|
||||
|
||||
@staticmethod
|
||||
def get_user_room(user_id: int) -> str:
|
||||
"""Get the room name for a specific user."""
|
||||
return f"user_{user_id}"
|
||||
|
||||
@staticmethod
|
||||
def emit_to_user(user_id: int, event: str, data: dict) -> None:
|
||||
"""Emit an event to a specific user's room."""
|
||||
room = SocketIOService.get_user_room(user_id)
|
||||
socketio.emit(event, data, room=room)
|
||||
logger.debug(f"Emitted {event} to user {user_id} in room {room}")
|
||||
|
||||
@staticmethod
|
||||
def emit_credits_changed(user_id: int, new_credits: int) -> None:
|
||||
"""Emit credits_changed event to a user."""
|
||||
SocketIOService.emit_to_user(
|
||||
user_id,
|
||||
"credits_changed",
|
||||
{"credits": new_credits},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_user_from_socketio() -> dict | None:
|
||||
"""Get user from SocketIO connection using cookies."""
|
||||
try:
|
||||
from flask import current_app
|
||||
from flask_jwt_extended import decode_token
|
||||
|
||||
# Check if we have the access_token cookie
|
||||
access_token = request.cookies.get("access_token_cookie")
|
||||
if not access_token:
|
||||
return None
|
||||
|
||||
# Decode the JWT token manually
|
||||
with current_app.app_context():
|
||||
try:
|
||||
decoded_token = decode_token(access_token)
|
||||
current_user_id = decoded_token["sub"]
|
||||
if not current_user_id:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# Query database for user data
|
||||
from app.models.user import User
|
||||
user = User.query.get(int(current_user_id))
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
return {
|
||||
"id": str(user.id),
|
||||
"email": user.email,
|
||||
"name": user.name,
|
||||
"role": user.role,
|
||||
"credits": user.credits,
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@socketio.on("connect")
|
||||
def handle_connect(auth=None) -> bool:
|
||||
"""Handle client connection."""
|
||||
try:
|
||||
logger.info(f"SocketIO connection from {request.remote_addr}")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error handling SocketIO connection")
|
||||
disconnect()
|
||||
return False
|
||||
|
||||
|
||||
@socketio.on("authenticate")
|
||||
def handle_authenticate(data):
|
||||
"""Handle authentication after connection."""
|
||||
try:
|
||||
user = SocketIOService.get_user_from_socketio()
|
||||
if not user:
|
||||
emit("auth_error", {"error": "Authentication failed"})
|
||||
disconnect()
|
||||
return
|
||||
|
||||
user_id = int(user["id"])
|
||||
user_room = SocketIOService.get_user_room(user_id)
|
||||
|
||||
# Join user-specific room
|
||||
join_room(user_room)
|
||||
|
||||
logger.info(f"User {user_id} authenticated and joined room {user_room}")
|
||||
|
||||
# Send current credits on authentication
|
||||
emit("auth_success", {"user": user})
|
||||
emit("credits_changed", {"credits": user["credits"]})
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error handling SocketIO authentication")
|
||||
emit("auth_error", {"error": "Authentication failed"})
|
||||
disconnect()
|
||||
|
||||
|
||||
@socketio.on("disconnect")
|
||||
def handle_disconnect() -> None:
|
||||
"""Handle client disconnection."""
|
||||
try:
|
||||
user = SocketIOService.get_user_from_socketio()
|
||||
if user:
|
||||
user_id = int(user["id"])
|
||||
user_room = SocketIOService.get_user_room(user_id)
|
||||
leave_room(user_room)
|
||||
logger.info(f"User {user_id} disconnected from SocketIO")
|
||||
|
||||
except Exception:
|
||||
logger.exception("Error handling SocketIO disconnection")
|
||||
|
||||
|
||||
# Export the service instance
|
||||
socketio_service = SocketIOService()
|
||||
Reference in New Issue
Block a user