|
from datetime import datetime |
|
from typing import Dict, Optional |
|
from app.models.session import ( |
|
UserSession, |
|
WagonProgress, |
|
Conversation, |
|
Message, |
|
GuessingProgress, |
|
) |
|
from app.core.logging import LoggerMixin |
|
import uuid |
|
from app.utils.file_management import FileManager |
|
|
|
|
|
|
|
class SessionService(LoggerMixin): |
|
|
|
_sessions: Dict[str, UserSession] = {} |
|
|
|
@classmethod |
|
def create_session(cls) -> UserSession: |
|
"""Create a new session""" |
|
session_id = str(uuid.uuid4()) |
|
|
|
session = UserSession( |
|
session_id=session_id, |
|
created_at=datetime.utcnow(), |
|
last_active=datetime.utcnow(), |
|
default_game=True |
|
) |
|
|
|
cls._sessions[session_id] = session |
|
cls.get_logger().info(f"Created new session: {session_id}") |
|
return session |
|
|
|
@classmethod |
|
def get_session(cls, session_id: str) -> Optional[UserSession]: |
|
"""Get session by ID""" |
|
session = cls._sessions.get(session_id) |
|
if session: |
|
session.last_active = datetime.utcnow() |
|
cls.get_logger().debug(f"Retrieved session: {session_id}") |
|
else: |
|
cls.get_logger().warning(f"Session not found: {session_id}") |
|
return session |
|
|
|
@classmethod |
|
def update_session(cls, session: UserSession) -> None: |
|
"""Update a session's last active timestamp""" |
|
session.last_active = datetime.utcnow() |
|
cls._sessions[session.session_id] = session |
|
cls.get_logger().debug( |
|
f"Updated session | session_id: {session.session_id} | current_wagon: {session.current_wagon.wagon_id}" |
|
) |
|
|
|
@classmethod |
|
def add_message( |
|
cls, session_id: str, uid: str, message: Message |
|
) -> Optional[Conversation]: |
|
"""Add a message to a character's conversation""" |
|
session = cls.get_session(session_id) |
|
if not session: |
|
cls.get_logger().error( |
|
f"Failed to add message - session not found | session_id: {session_id} | uid: {uid}" |
|
) |
|
return None |
|
|
|
|
|
|
|
wagon_id = int(uid.split("-")[1]) |
|
|
|
|
|
|
|
if wagon_id != session.current_wagon.wagon_id: |
|
cls.get_logger().error( |
|
f"Cannot add message - wrong wagon | session_id: {session_id} | uid: {uid} | current_wagon: {session.current_wagon.wagon_id}" |
|
) |
|
return None |
|
|
|
|
|
if uid not in session.current_wagon.conversations: |
|
cls.get_logger().info( |
|
f"Starting new conversation | session_id: {session_id} | uid: {uid} | wagon_id: {wagon_id}" |
|
) |
|
session.current_wagon.conversations[uid] = Conversation(uid=uid) |
|
|
|
|
|
conversation = session.current_wagon.conversations[uid] |
|
conversation.messages.append(message) |
|
conversation.last_interaction = datetime.utcnow() |
|
|
|
cls.update_session(session) |
|
cls.get_logger().debug( |
|
f"Added message to conversation | session_id: {session_id} | uid: {uid} | message_role: {message.role} | message_length: {len(message.content)}" |
|
) |
|
return conversation |
|
|
|
@classmethod |
|
def get_conversation(cls, session_id: str, uid: str) -> Optional[Conversation]: |
|
"""Get a conversation with a specific character""" |
|
session = cls.get_session(session_id) |
|
if not session: |
|
cls.get_logger().error( |
|
f"Failed to get conversation - session not found | session_id: {session_id} | uid: {uid}" |
|
) |
|
return None |
|
|
|
|
|
|
|
|
|
wagon_id = int(uid.split("-")[1]) |
|
|
|
|
|
|
|
if wagon_id != session.current_wagon.wagon_id: |
|
cls.get_logger().warning( |
|
f"Cannot get conversation - wrong wagon | session_id: {session_id} | uid: {uid} | current_wagon: {session.current_wagon.wagon_id}" |
|
) |
|
return None |
|
|
|
|
|
conversation = session.current_wagon.conversations.get(uid) |
|
|
|
if conversation: |
|
cls.get_logger().debug( |
|
f"Retrieved conversation | session_id: {session_id} | uid: {uid} | message_count: {len(conversation.messages)}" |
|
) |
|
else: |
|
cls.get_logger().debug( |
|
f"No conversation found | session_id: {session_id} | uid: {uid}" |
|
) |
|
return conversation |
|
|
|
@classmethod |
|
def get_guessing_progress(cls, session_id: str) -> GuessingProgress: |
|
session = cls.get_session(session_id) |
|
if not session: |
|
cls.get_logger().error( |
|
f"Failed to get guesses - session not found | session_id: {session_id}" |
|
) |
|
return None |
|
return session.guessing_progress |
|
|
|
@classmethod |
|
def update_guessing_progress( |
|
cls, session_id: str, indication: str, guess: str, thought: list[str] |
|
) -> None: |
|
session = cls.get_session(session_id) |
|
if not session: |
|
cls.get_logger().error( |
|
f"Failed to get the guessing progress - session not found | session_id: {session_id}" |
|
) |
|
return |
|
|
|
session.guessing_progress.guesses.append(guess) |
|
session.guessing_progress.indications.append( |
|
Message( |
|
role="user", |
|
content=indication, |
|
) |
|
) |
|
|
|
wagon_id = session.current_wagon.wagon_id |
|
|
|
if ( |
|
session.current_wagon.conversations.get(f"wagon-{wagon_id}-player-0") |
|
is None |
|
): |
|
session.current_wagon.conversations[f"wagon-{wagon_id}-player-0"] = ( |
|
Conversation(uid="player-0") |
|
) |
|
|
|
messages = session.current_wagon.conversations.get( |
|
f"wagon-{wagon_id}-player-0" |
|
).messages |
|
|
|
messages.append(Message(role="user", content=indication)) |
|
messages.append(Message(role="assistant", content=thought[0])) |
|
|
|
cls.update_session(session) |
|
cls.get_logger().info(f"Added a new guess | session_id: {session_id}") |
|
|
|
@classmethod |
|
def advance_wagon(cls, session_id: str) -> bool: |
|
"""Advance to the next wagon""" |
|
cls.get_logger().info(f"Attempting to advance wagon | session_id={session_id}") |
|
|
|
|
|
session = cls.get_session(session_id) |
|
if not session: |
|
cls.get_logger().error(f"Failed to advance wagon - session not found | session_id={session_id}") |
|
return False |
|
|
|
current_wagon_id = session.current_wagon.wagon_id |
|
cls.get_logger().debug(f"Current wagon state | session_id={session_id} | current_wagon_id={current_wagon_id}") |
|
|
|
try: |
|
|
|
cls.get_logger().debug(f"Loading session data | session_id={session_id} | default_game={session.default_game}") |
|
next_wagon_id = current_wagon_id + 1 |
|
_, _, wagons = FileManager.load_session_data(session_id, session.default_game) |
|
max_wagons = len(wagons) |
|
|
|
|
|
if next_wagon_id > max_wagons - 1: |
|
cls.get_logger().warning( |
|
f"Cannot advance - already at last wagon | session_id={session_id} | current_wagon={current_wagon_id} | max_wagons={max_wagons}" |
|
) |
|
raise Exception("Cannot advance - already at last wagon") |
|
|
|
cls.get_logger().debug( |
|
f"Wagon progression details | session_id={session_id} | current_wagon={current_wagon_id} | next_wagon={next_wagon_id} | max_wagons={max_wagons}" |
|
) |
|
|
|
|
|
current_wagon = wagons[next_wagon_id] |
|
|
|
|
|
session.current_wagon = WagonProgress( |
|
wagon_id=next_wagon_id, |
|
theme=current_wagon["theme"], |
|
password=current_wagon["passcode"], |
|
) |
|
|
|
|
|
session.guessing_progress = GuessingProgress() |
|
cls.update_session(session) |
|
|
|
cls.get_logger().info( |
|
f"Successfully advanced to next wagon | session_id={session_id} | previous_wagon={current_wagon_id} | new_wagon={next_wagon_id} | theme={current_wagon['theme']}" |
|
) |
|
return True |
|
|
|
except FileNotFoundError as e: |
|
cls.get_logger().error( |
|
f"Failed to load session data | session_id={session_id} | error={str(e)} | error_type=FileNotFoundError" |
|
) |
|
return False |
|
except KeyError as e: |
|
cls.get_logger().error( |
|
f"Invalid wagon data structure | session_id={session_id} | error={str(e)} | error_type=KeyError" |
|
) |
|
return False |
|
except Exception as e: |
|
cls.get_logger().error( |
|
f"Unexpected error during wagon advancement | session_id={session_id} | error={str(e)} | error_type={type(e).__name__}" |
|
) |
|
return False |
|
|
|
@classmethod |
|
def cleanup_old_sessions(cls, max_age_hours: int = 24) -> None: |
|
"""Remove sessions older than specified hours""" |
|
current_time = datetime.utcnow() |
|
sessions_to_remove = [] |
|
|
|
for session_id, session in cls._sessions.items(): |
|
age = (current_time - session.last_active).total_seconds() / 3600 |
|
if age > max_age_hours: |
|
sessions_to_remove.append(session_id) |
|
cls.get_logger().info( |
|
f"Marking session for cleanup | session_id: {session_id} | age_hours: {age}" |
|
) |
|
|
|
for session_id in sessions_to_remove: |
|
cls.terminate_session(session_id) |
|
cls.get_logger().info(f"Cleaned up old session | session_id: {session_id}") |
|
|
|
@classmethod |
|
def terminate_session(cls, session_id: str) -> None: |
|
"""Terminate a session and clean up its resources""" |
|
if session_id in cls._sessions: |
|
del cls._sessions[session_id] |
|
cls.get_logger().info(f"Terminated session: {session_id}") |
|
|