from flask_socketio import SocketIO, emit, join_room, leave_room from flask import request, current_app from app.services import SessionService import threading class SocketController: def __init__( self, socketio: SocketIO, session_service: SessionService, ): self.socketio = socketio self.session_service = session_service self._register_events() def _register_events(self): @self.socketio.on("connect") def on_connect(): try: current_app.logger.info(f"Client connected: {request.sid}") emit("connection_response", {"status": "connected", "sid": request.sid}) except Exception as e: emit("error", {"message": f"Connect error: {str(e)}"}) current_app.logger.error(f"Connect error: {str(e)}") @self.socketio.on("disconnect") def on_disconnect(): try: current_app.logger.info(f"Client disconnected: {request.sid}") except Exception as e: emit("error", {"message": f"Disconnect error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("join_room") def handle_join_room(data): try: session_code = data.get("session_code") user_id = data.get("user_id") if not session_code or not user_id: emit("error", {"message": "session_code and user_id are required"}) return session = self.session_service.join_session(session_code, user_id) if session is None: emit( "error", {"message": "Failed to join session or session inactive"}, ) return session_id = session["session_id"] join_room(session_id) message = ( "Admin has joined the room." if session["is_admin"] else f"User {session['username']} has joined the room." ) current_app.logger.info(f"Client joined: {message}") emit( "room_message", { "type": "join", "message": message, "room": session_id, "argument": "adm_update", "data": { "session_info": session["session_info"], "quiz_info": session["quiz_info"], }, }, to=request.sid, ) emit( "room_message", { "type": "participan_join", "message": message, "room": session_id, "argument": "adm_update", "data": { "participants": session["session_info"]["participants"], }, }, room=session_id, skip_sid=request.sid, ) except Exception as e: emit("error", {"message": f"Join room error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("leave_room") def handle_leave_room(data): try: session_id = data.get("session_id") user_id = data.get("user_id") username = data.get("username", "anonymous") leave_result = self.session_service.leave_session(session_id, user_id) leave_room(session_id) if leave_result["is_success"]: emit( "room_message", { "type": "participan_leave", "message": f"{username} has left the room.", "room": session_id, "argument": "adm_update", "data": { "participants": leave_result["participants"], }, }, room=session_id, skip_sid=request.sid, ) emit( "room_message", { "type": "leave", "message": f"{username} has left the room.", "room": session_id, "argument": "adm_update", "data": None, }, room=session_id, to=request.sid, ) except Exception as e: emit("error", {"message": f"Leave room error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("send_message") def on_send_message(data): try: session_code = data.get("session_id") message = data.get("message") username = data.get("username", "anonymous") emit( "receive_message", {"message": message, "from": username}, room=session_code, ) except Exception as e: emit("error", {"message": f"Send message error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("end_session") def handle_end_session(data): try: session_code = data.get("session_id") user_id = data.get("user_id") if not session_code or not user_id: emit("error", {"message": "session_id and user_id required"}) return self.session_service.end_session( session_id=session_code, user_id=user_id ) for key in [ self._answers_key(session_code), self._scores_key(session_code), self._questions_key(session_code), ]: self.redis_repo.delete_key(key) emit( "room_closed", {"message": "Session has ended.", "room": session_code}, room=session_code, ) except Exception as e: emit("error", {"message": f"End session error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("start_quiz") def handle_start_quiz(data): try: session_id = data.get("session_id") if not session_id: emit("error", {"message": "session_id is required"}) return emit("quiz_started", {"message": "Quiz has started!"}, room=session_id) threading.Thread( target=self.session_service.run_quiz_flow, args=(session_id, self.socketio), daemon=True, ).start() except Exception as e: emit("error", {"message": f"Start quiz error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}") @self.socketio.on("submit_answer") def handle_submit_answer(data): try: session_id = data.get("session_id") user_id = data.get("user_id") question_index = data.get("question_index") user_answer = data.get("answer") time_spent = data.get("time_spent") if not all( [ session_id, user_id, question_index is not None, user_answer is not None, time_spent is not None, ] ): emit( "error", { "message": "session_id, user_id, question_index, and answer are required" }, ) return result = self.session_service.submit_answer( session_id=session_id, user_id=user_id, question_index=question_index, answer=user_answer, time_spent=time_spent, ) emit( "answer_submitted", { "question_index": result["question_index"], "answer": result["answer"], "correct": result["correct"], "score": result["scores"], }, to=request.sid, ) emit( "score_update", { "scores": self.session_service.get_ranked_scores(session_id), }, room=session_id, ) except ValueError as exc: emit("error", {"message": str(exc)}) current_app.logger.error(f"error: {str(exc)}") except Exception as e: emit("error", {"message": f"Submit answer error: {str(e)}"}) current_app.logger.error(f"error: {str(e)}")