fix: session sistem

This commit is contained in:
akhdanre 2025-05-16 02:42:10 +07:00
parent aae53cccce
commit 5ac945eb18
7 changed files with 215 additions and 43 deletions

View File

@ -7,35 +7,14 @@ import json
from redis import Redis from redis import Redis
class RedisRepository:
"""Small helper wrapper to (de)serialize python objects to Redis."""
def __init__(self, redis: Redis):
self.redis = redis
def set_data(self, key: str, value):
self.redis.set(key, json.dumps(value))
def get_data(self, key: str):
data = self.redis.get(key)
return json.loads(data) if data else None
def delete_key(self, key: str):
self.redis.delete(key)
class SocketController: class SocketController:
def __init__( def __init__(
self, self,
socketio: SocketIO, socketio: SocketIO,
redis: Redis,
session_service: SessionService, session_service: SessionService,
): ):
self.socketio = socketio self.socketio = socketio
self.session_service = session_service self.session_service = session_service
self.redis_repo = RedisRepository(redis)
# Menyimpan SID admin untuk setiap session \u2192 {session_code: sid}
self.admin_sids: dict[str, str] = {}
self._register_events() self._register_events()
@staticmethod @staticmethod
@ -101,22 +80,21 @@ class SocketController:
return return
session = self.session_service.join_session( session = self.session_service.join_session(
session_code=session_code, user_id=user_id session_code=session_code,
user_id=user_id,
) )
if session is None: if session is None:
emit("error", {"message": "Failed to join session or session inactive"}) emit("error", {"message": "Failed to join session or session inactive"})
return return
join_room(session_code) join_room(session_code)
# Kalau user ini admin, simpan SIDnya.
if session["is_admin"]: if session["is_admin"]:
self.admin_sids[session_code] = request.sid
message = "Admin has joined the room." message = "Admin has joined the room."
else: else:
message = f"User {session['username']} has joined the room." message = f"User {session['username']} has joined the room."
print(message)
emit( emit(
"room_message", "room_message",
{ {
@ -124,9 +102,22 @@ class SocketController:
"message": message, "message": message,
"room": session_code, "room": session_code,
"argument": "adm_update", "argument": "adm_update",
"data": session if not session["is_admin"] else None, "data": session["quiz_info"],
},
to=request.sid,
)
emit(
"room_message",
{
"type": "participan_join",
"message": message,
"room": session_code,
"argument": "adm_update",
"data": session["quiz_info"]["participants"],
}, },
room=session_code, room=session_code,
skip_sid=request.sid, # Skip user yang baru join
) )
@self.socketio.on("submit_answer") @self.socketio.on("submit_answer")

View File

@ -6,6 +6,7 @@ from app.repositories import (
SubjectRepository, SubjectRepository,
SessionRepository, SessionRepository,
NERSRLRepository, NERSRLRepository,
SessionMemoryRepository,
) )
from app.services import ( from app.services import (
@ -44,6 +45,7 @@ class Container(containers.DeclarativeContainer):
subject_repository = providers.Factory(SubjectRepository, mongo.provided.db) subject_repository = providers.Factory(SubjectRepository, mongo.provided.db)
session_repository = providers.Factory(SessionRepository, mongo.provided.db) session_repository = providers.Factory(SessionRepository, mongo.provided.db)
ner_srl_repository = providers.Factory(NERSRLRepository) ner_srl_repository = providers.Factory(NERSRLRepository)
session_memory_repository = providers.Factory(SessionMemoryRepository, redis)
# services # services
auth_service = providers.Factory( auth_service = providers.Factory(
@ -83,6 +85,7 @@ class Container(containers.DeclarativeContainer):
session_service = providers.Factory( session_service = providers.Factory(
SessionService, SessionService,
session_repository, session_repository,
session_memory_repository,
user_repository, user_repository,
) )
@ -104,7 +107,6 @@ class Container(containers.DeclarativeContainer):
socket_controller = providers.Factory( socket_controller = providers.Factory(
SocketController, SocketController,
socketio, socketio,
redis,
session_service, session_service,
) )
session_controller = providers.Factory(SessionController, session_service) session_controller = providers.Factory(SessionController, session_service)

View File

@ -14,5 +14,5 @@ class SessionEntity(BaseModel):
ended_at: datetime | None = None ended_at: datetime | None = None
is_active: bool = True is_active: bool = True
participan_limit: int = 10 participan_limit: int = 10
participants: List[str] = [] participants: List[dict] = []
current_question_index: int = 0 current_question_index: int = 0

View File

@ -4,6 +4,7 @@ from .answer_repository import UserAnswerRepository
from .subject_repository import SubjectRepository from .subject_repository import SubjectRepository
from .session_repostory import SessionRepository from .session_repostory import SessionRepository
from .ner_srl_repository import NERSRLRepository from .ner_srl_repository import NERSRLRepository
from .session_memory_repository import SessionMemoryRepository
__all__ = [ __all__ = [
"UserRepository", "UserRepository",
@ -12,4 +13,5 @@ __all__ = [
"SubjectRepository", "SubjectRepository",
"SessionRepository", "SessionRepository",
"NERSRLRepository", "NERSRLRepository",
"SessionMemoryRepository",
] ]

View File

@ -0,0 +1,149 @@
import json
from typing import Dict, Any, List, Optional
from redis import Redis
from app.helpers import DatetimeUtil
from app.models.entities import SessionEntity
class SessionMemoryRepository:
def __init__(self, redis: Redis):
self.redis = redis
def set_data(self, key: str, value: Any):
"""
Set data in Redis
:param key: Redis key
:param value: Value to store
"""
self.redis.set(key, json.dumps(value))
def get_data(self, key: str) -> Optional[Any]:
"""
Get data from Redis
:param key: Redis key
:return: Decoded JSON data or None
"""
data = self.redis.get(key)
return json.loads(data) if data else None
def delete_key(self, key: str):
"""
Delete a key from Redis
:param key: Redis key to delete
"""
self.redis.delete(key)
def create_session(self, session_id: str, initial_data: SessionEntity) -> str:
"""
Create a new session
:param session_id: ID for the session
:param initial_data: Initial data for the session
:return: Session ID
"""
data = initial_data.model_dump()
data["created_at"] = str(data["created_at"])
self.set_data(f"session:{session_id}", data)
return session_id
def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
"""
Retrieve a session by its ID
:param session_id: ID of the session
:return: Session data or None if not found
"""
return self.get_data(f"session:{session_id}")
def close_session(
self, session_id: str, additional_data: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
"""
Close/end a session and return its data before deleting
:param session_id: ID of the session to close
:param additional_data: Optional extra data to add when closing
:return: Session data if found and closed, None otherwise
"""
# Ambil data session
session = self.get_data(f"session:{session_id}")
if not session:
return None
session["status"] = "closed"
session["closed_at"] = DatetimeUtil.now_iso
if additional_data:
session.update(additional_data)
try:
self.delete_key(f"session:{session_id}")
return session
except Exception as e:
print(f"Error closing session {session_id}: {e}")
return session
def find_session_by_code(self, session_code: str) -> Optional[Dict[str, Any]]:
"""
Find a session by its session code.
:param session_code: Session code to search for.
:return: Session data if found, None otherwise.
"""
# Dapatkan semua session keys
session_keys = self.redis.keys("session:*")
for key in session_keys:
session_data = self.get_data(key)
if session_data and session_data.get("session_code") == session_code:
return session_data
return None
def add_user_to_session(
self, session_id: str, user_data: Dict[str, Any] = None
) -> bool:
"""
Add a user to an existing session.
:param session_id: ID of the session.
:param user_id: ID of the user to add.
:param user_data: Optional additional data about the user in the session.
:return: True if user was added successfully, False if session doesn't exist.
"""
session = self.get_session(session_id)
if not session:
return False
user_entry = {
**(user_data or {}),
"joined_at": DatetimeUtil.now_iso(),
}
print("Final user_entry:", user_entry)
existing_users = session.get("participants", [])
existing_users.append(user_entry)
session["participants"] = existing_users
self.set_data(f"session:{session_id}", session)
return True
def remove_user_from_session(self, session_id: str, user_id: str) -> bool:
"""
Remove a user from a session
:param session_id: ID of the session
:param user_id: ID of the user to remove
:return: True if user was removed, False if session doesn't exist or user not in session
"""
session = self.get_data(f"session:{session_id}")
if not session:
return False
original_users_count = len(session.get("participans", []))
session["participans"] = [
user for user in session.get("participans", []) if user["id"] != user_id
]
if len(session["participans"]) < original_users_count:
self.set_data(f"session:{session_id}", session)
return True
return False

View File

@ -12,7 +12,6 @@ class SessionRepository:
# self.collection.create_index("id", unique=True) # self.collection.create_index("id", unique=True)
def insert(self, session_data: SessionEntity) -> str: def insert(self, session_data: SessionEntity) -> str:
print(session_data.model_dump(by_alias=True, exclude_none=True))
result = self.collection.insert_one( result = self.collection.insert_one(
session_data.model_dump(by_alias=True, exclude_none=True) session_data.model_dump(by_alias=True, exclude_none=True)
) )

View File

@ -1,13 +1,19 @@
from typing import Optional from typing import Optional
from uuid import uuid4 from uuid import uuid4
from app.repositories import SessionRepository, UserRepository from app.repositories import SessionRepository, UserRepository, SessionMemoryRepository
from app.models.entities import SessionEntity from app.models.entities import SessionEntity
from app.helpers import DatetimeUtil from app.helpers import DatetimeUtil
class SessionService: class SessionService:
def __init__(self, repository: SessionRepository, user_repository: UserRepository): def __init__(
self.repository = repository self,
repository_mongo: SessionRepository,
repository_redis: SessionMemoryRepository,
user_repository: UserRepository,
):
self.repository_mongo = repository_mongo
self.repository_redis = repository_redis
self.user_repository = user_repository self.user_repository = user_repository
def create_session(self, quiz_id: str, host_id: str, limit_participan: int) -> str: def create_session(self, quiz_id: str, host_id: str, limit_participan: int) -> str:
@ -22,36 +28,59 @@ class SessionService:
current_question_index=0, current_question_index=0,
is_active=True, is_active=True,
) )
session_id = self.repository_mongo.insert(session)
session.id = session_id
self.repository_redis.create_session(session_id, session)
return { return {
"session_id": self.repository.insert(session), "session_id": session_id,
"session_code": generateed_code, "session_code": generateed_code,
} }
def join_session(self, session_code: str, user_id: str) -> dict: def join_session(self, session_code: str, user_id: str) -> dict:
user = self.user_repository.get_user_by_id(user_id) user = self.user_repository.get_user_by_id(user_id)
session = self.repository.find_by_session_code(session_code=session_code) session = self.repository_redis.find_session_by_code(session_code)
if session is None or session.is_active == False: if session is None:
return None return None
if session.host_id == user_id: is_existing_user = any(
return {"is_admin": True, "message": "admin joined"} u["id"] == user_id for u in session.get("participants", [])
)
if session["host_id"] == user_id:
return {"is_admin": True, "message": "admin joined", "quiz_info": session}
if is_existing_user:
return {
"is_admin": False,
"user_id": str(user.id),
"username": user.name,
"user_pic": user.pic_url,
"quiz_info": session,
"new_user": not is_existing_user,
}
self.repository_redis.add_user_to_session(
session_id=session["id"],
user_data={
"id": str(user.id),
"username": user.name,
"user_pic": user.pic_url,
},
)
session = self.repository_redis.get_session(session["id"])
if user_id not in session.participants:
session.participants.append(user_id)
self.repository.update(session.id, {"participants": session.participants})
response = { response = {
"is_admin": False, "is_admin": False,
"user_id": str(user.id), "user_id": str(user.id),
"username": user.name, "username": user.name,
"user_pic": user.pic_url, "user_pic": user.pic_url,
"session_id": str(session.id), "quiz_info": session if not is_existing_user else None,
"new_user": not is_existing_user,
} }
return response return response
def leave_session(self, session_id: str, user_id: str) -> dict: def leave_session(self, session_id: str, user_id: str) -> dict:
session = self.repository.get_by_id(session_id) session = self.repository_mongo.get_by_id(session_id)
if session is None: if session is None:
return {"error": "Session not found"} return {"error": "Session not found"}