From b17e0db2b09cf6ca99b1d0f4c9cf791b805db551 Mon Sep 17 00:00:00 2001 From: JSC Date: Sun, 13 Jul 2025 01:46:23 +0200 Subject: [PATCH] feat: Emit credits required event via SocketIO when user lacks sufficient credits --- app/services/decorators.py | 16 ++++++ app/services/socketio_service.py | 86 ++++++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 16 deletions(-) diff --git a/app/services/decorators.py b/app/services/decorators.py index e2dbb75..4d4ff44 100644 --- a/app/services/decorators.py +++ b/app/services/decorators.py @@ -172,6 +172,22 @@ def require_credits(credits_needed: int): # Check if user has enough credits if user.credits < credits_needed: + # Emit credits required event via SocketIO + try: + from app.services.socketio_service import socketio_service + + socketio_service.emit_credits_required( + user.id, credits_needed + ) + 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_required event: {e}" + ) + return ( jsonify( { diff --git a/app/services/socketio_service.py b/app/services/socketio_service.py index d33e0a2..7cf2491 100644 --- a/app/services/socketio_service.py +++ b/app/services/socketio_service.py @@ -6,6 +6,7 @@ from flask import request from flask_socketio import disconnect, emit, join_room, leave_room from app import socketio +from app.services.decorators import require_credits logger = logging.getLogger(__name__) @@ -30,7 +31,9 @@ class SocketIOService: """Emit an event to all connected clients.""" try: socketio.emit(event, data) - logger.info(f"Successfully emitted {event} to all clients with data keys: {list(data.keys())}") + logger.info( + f"Successfully emitted {event} to all clients with data keys: {list(data.keys())}" + ) except Exception as e: logger.error(f"Failed to emit {event}: {e}") @@ -43,6 +46,15 @@ class SocketIOService: {"credits": new_credits}, ) + @staticmethod + def emit_credits_required(user_id: int, credits_needed: int) -> None: + """Emit an event when credits are required.""" + SocketIOService.emit_to_user( + user_id, + "credits_required", + {"credits_needed": credits_needed}, + ) + @staticmethod def get_user_from_socketio() -> dict | None: """Get user from SocketIO connection using cookies.""" @@ -52,8 +64,10 @@ class SocketIOService: # Check if we have the access_token cookie access_token = request.cookies.get("access_token_cookie") - logger.debug(f"Access token from cookies: {access_token[:20] if access_token else None}...") - + logger.debug( + f"Access token from cookies: {access_token[:20] if access_token else None}..." + ) + if not access_token: logger.debug("No access token found in cookies") return None @@ -64,7 +78,7 @@ class SocketIOService: decoded_token = decode_token(access_token) current_user_id = decoded_token["sub"] logger.debug(f"Decoded user ID: {current_user_id}") - + if not current_user_id: logger.debug("No user ID in token") return None @@ -77,7 +91,9 @@ class SocketIOService: user = User.query.get(int(current_user_id)) if not user or not user.is_active: - logger.debug(f"User not found or inactive: {current_user_id}") + logger.debug( + f"User not found or inactive: {current_user_id}" + ) return None logger.debug(f"Successfully found user: {user.email}") @@ -97,7 +113,9 @@ class SocketIOService: def handle_connect(auth=None): """Handle client connection.""" try: - logger.info(f"SocketIO connection established from {request.remote_addr}") + logger.info( + f"SocketIO connection established from {request.remote_addr}" + ) logger.info(f"Session ID: {request.sid}") except Exception: @@ -110,10 +128,10 @@ def handle_authenticate(data): """Handle authentication after connection.""" try: user = SocketIOService.get_user_from_socketio() - + if not user: logger.warning("SocketIO authentication failed - no user found") - emit("auth_error", {"error": "Authentication failed"}) + # emit("auth_error", {"error": "Authentication failed"}) disconnect() return @@ -126,20 +144,56 @@ def handle_authenticate(data): 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"]}) + SocketIOService.emit_to_user(user_id, "auth_success", {"user": user}) + SocketIOService.emit_to_user( + user_id, "credits_changed", {"credits": user["credits"]} + ) except Exception: logger.exception("Error handling SocketIO authentication") - emit("auth_error", {"error": "Authentication failed"}) + # emit("auth_error", {"error": "Authentication failed"}) disconnect() -@socketio.on("test_event") -def handle_test_event(data): - """Test handler to verify SocketIO events are working.""" - logger.debug(f"Test event received: {data}") - emit("test_response", {"message": "Test event received successfully"}) +@socketio.on("play_sound") +@require_credits(1) +def handle_play_sound(data): + """Handle play_sound event from client.""" + try: + user = SocketIOService.get_user_from_socketio() + + if not user: + logger.warning("SocketIO play_sound failed - no authenticated user") + # emit("error", {"message": "Authentication required"}) + return + + user_id = int(user["id"]) + sound_id = data.get("soundId") + if not sound_id: + logger.warning("SocketIO play_sound failed - no soundId provided") + SocketIOService.emit_to_user( + user_id, "error", {"message": "Sound ID required"} + ) + return + + # Import and use the VLC service to play the sound + from app.services.vlc_service import vlc_service + + logger.info(f"User {user_id} playing sound {sound_id} via SocketIO") + + # Play the sound using the VLC service + success = vlc_service.play_sound(sound_id, user_id) + + if not success: + SocketIOService.emit_to_user( + user_id, + "error", + {"message": f"Failed to play sound {sound_id}"}, + ) + + except Exception as e: + logger.exception(f"Error handling play_sound event: {e}") + # emit("error", {"message": "Failed to play sound"}) @socketio.on("disconnect")