feat: Emit credits required event via SocketIO when user lacks sufficient credits

This commit is contained in:
JSC
2025-07-13 01:46:23 +02:00
parent 64074685a3
commit b17e0db2b0
2 changed files with 86 additions and 16 deletions

View File

@@ -172,6 +172,22 @@ def require_credits(credits_needed: int):
# Check if user has enough credits # Check if user has enough credits
if user.credits < credits_needed: 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 ( return (
jsonify( jsonify(
{ {

View File

@@ -6,6 +6,7 @@ from flask import request
from flask_socketio import disconnect, emit, join_room, leave_room from flask_socketio import disconnect, emit, join_room, leave_room
from app import socketio from app import socketio
from app.services.decorators import require_credits
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -30,7 +31,9 @@ class SocketIOService:
"""Emit an event to all connected clients.""" """Emit an event to all connected clients."""
try: try:
socketio.emit(event, data) 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: except Exception as e:
logger.error(f"Failed to emit {event}: {e}") logger.error(f"Failed to emit {event}: {e}")
@@ -43,6 +46,15 @@ class SocketIOService:
{"credits": new_credits}, {"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 @staticmethod
def get_user_from_socketio() -> dict | None: def get_user_from_socketio() -> dict | None:
"""Get user from SocketIO connection using cookies.""" """Get user from SocketIO connection using cookies."""
@@ -52,8 +64,10 @@ class SocketIOService:
# Check if we have the access_token cookie # Check if we have the access_token cookie
access_token = request.cookies.get("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: if not access_token:
logger.debug("No access token found in cookies") logger.debug("No access token found in cookies")
return None return None
@@ -64,7 +78,7 @@ class SocketIOService:
decoded_token = decode_token(access_token) decoded_token = decode_token(access_token)
current_user_id = decoded_token["sub"] current_user_id = decoded_token["sub"]
logger.debug(f"Decoded user ID: {current_user_id}") logger.debug(f"Decoded user ID: {current_user_id}")
if not current_user_id: if not current_user_id:
logger.debug("No user ID in token") logger.debug("No user ID in token")
return None return None
@@ -77,7 +91,9 @@ class SocketIOService:
user = User.query.get(int(current_user_id)) user = User.query.get(int(current_user_id))
if not user or not user.is_active: 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 return None
logger.debug(f"Successfully found user: {user.email}") logger.debug(f"Successfully found user: {user.email}")
@@ -97,7 +113,9 @@ class SocketIOService:
def handle_connect(auth=None): def handle_connect(auth=None):
"""Handle client connection.""" """Handle client connection."""
try: 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}") logger.info(f"Session ID: {request.sid}")
except Exception: except Exception:
@@ -110,10 +128,10 @@ def handle_authenticate(data):
"""Handle authentication after connection.""" """Handle authentication after connection."""
try: try:
user = SocketIOService.get_user_from_socketio() user = SocketIOService.get_user_from_socketio()
if not user: if not user:
logger.warning("SocketIO authentication failed - no user found") logger.warning("SocketIO authentication failed - no user found")
emit("auth_error", {"error": "Authentication failed"}) # emit("auth_error", {"error": "Authentication failed"})
disconnect() disconnect()
return return
@@ -126,20 +144,56 @@ def handle_authenticate(data):
logger.info(f"User {user_id} authenticated and joined room {user_room}") logger.info(f"User {user_id} authenticated and joined room {user_room}")
# Send current credits on authentication # Send current credits on authentication
emit("auth_success", {"user": user}) SocketIOService.emit_to_user(user_id, "auth_success", {"user": user})
emit("credits_changed", {"credits": user["credits"]}) SocketIOService.emit_to_user(
user_id, "credits_changed", {"credits": user["credits"]}
)
except Exception: except Exception:
logger.exception("Error handling SocketIO authentication") logger.exception("Error handling SocketIO authentication")
emit("auth_error", {"error": "Authentication failed"}) # emit("auth_error", {"error": "Authentication failed"})
disconnect() disconnect()
@socketio.on("test_event") @socketio.on("play_sound")
def handle_test_event(data): @require_credits(1)
"""Test handler to verify SocketIO events are working.""" def handle_play_sound(data):
logger.debug(f"Test event received: {data}") """Handle play_sound event from client."""
emit("test_response", {"message": "Test event received successfully"}) 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") @socketio.on("disconnect")