feat: Emit credits required event via SocketIO when user lacks sufficient credits
This commit is contained in:
@@ -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(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,7 +64,9 @@ 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")
|
||||||
@@ -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:
|
||||||
@@ -113,7 +131,7 @@ def handle_authenticate(data):
|
|||||||
|
|
||||||
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user