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
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:
def __init__(
self,
socketio: SocketIO,
redis: Redis,
session_service: SessionService,
):
self.socketio = socketio
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()
@staticmethod
@ -101,22 +80,21 @@ class SocketController:
return
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:
emit("error", {"message": "Failed to join session or session inactive"})
return
join_room(session_code)
# Kalau user ini admin, simpan SIDnya.
if session["is_admin"]:
self.admin_sids[session_code] = request.sid
message = "Admin has joined the room."
else:
message = f"User {session['username']} has joined the room."
print(message)
emit(
"room_message",
{
@ -124,9 +102,22 @@ class SocketController:
"message": message,
"room": session_code,
"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,
skip_sid=request.sid, # Skip user yang baru join
)
@self.socketio.on("submit_answer")

View File

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

View File

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

View File

@ -4,6 +4,7 @@ from .answer_repository import UserAnswerRepository
from .subject_repository import SubjectRepository
from .session_repostory import SessionRepository
from .ner_srl_repository import NERSRLRepository
from .session_memory_repository import SessionMemoryRepository
__all__ = [
"UserRepository",
@ -12,4 +13,5 @@ __all__ = [
"SubjectRepository",
"SessionRepository",
"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)
def insert(self, session_data: SessionEntity) -> str:
print(session_data.model_dump(by_alias=True, exclude_none=True))
result = self.collection.insert_one(
session_data.model_dump(by_alias=True, exclude_none=True)
)

View File

@ -1,13 +1,19 @@
from typing import Optional
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.helpers import DatetimeUtil
class SessionService:
def __init__(self, repository: SessionRepository, user_repository: UserRepository):
self.repository = repository
def __init__(
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
def create_session(self, quiz_id: str, host_id: str, limit_participan: int) -> str:
@ -22,36 +28,59 @@ class SessionService:
current_question_index=0,
is_active=True,
)
session_id = self.repository_mongo.insert(session)
session.id = session_id
self.repository_redis.create_session(session_id, session)
return {
"session_id": self.repository.insert(session),
"session_id": session_id,
"session_code": generateed_code,
}
def join_session(self, session_code: str, user_id: str) -> dict:
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
if session.host_id == user_id:
return {"is_admin": True, "message": "admin joined"}
is_existing_user = any(
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 = {
"is_admin": False,
"user_id": str(user.id),
"username": user.name,
"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
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:
return {"error": "Session not found"}