from dataclasses import dataclass from datetime import timedelta from uuid import uuid4 from domain.sandbox import SandboxSession from usecase.interface import Clock, Logger, SandboxRuntime, SandboxSessionRepository @dataclass(frozen=True, slots=True) class CreateSandboxCommand: chat_id: str class CreateSandbox: def __init__( self, repository: SandboxSessionRepository, runtime: SandboxRuntime, clock: Clock, logger: Logger, ttl: timedelta, ) -> None: self._repository = repository self._runtime = runtime self._clock = clock self._logger = logger self._ttl = ttl def execute(self, command: CreateSandboxCommand) -> SandboxSession: now = self._clock.now() session = self._repository.get_active_by_chat_id(command.chat_id) if session is not None and session.expires_at > now: self._logger.info( 'sandbox_reused', attrs={ 'chat_id': command.chat_id, 'session_id': session.session_id, 'container_id': session.container_id, }, ) return session if session is not None: self._logger.info( 'sandbox_replaced', attrs={ 'chat_id': command.chat_id, 'session_id': session.session_id, 'container_id': session.container_id, }, ) self._runtime.stop(session.container_id) self._repository.delete(session.session_id) new_session = self._runtime.create( session_id=_new_session_id(), chat_id=command.chat_id, expires_at=now + self._ttl, ) self._repository.save(new_session) self._logger.info( 'sandbox_created', attrs={ 'chat_id': command.chat_id, 'session_id': new_session.session_id, 'container_id': new_session.container_id, }, ) return new_session class CleanupExpiredSandboxes: def __init__( self, repository: SandboxSessionRepository, runtime: SandboxRuntime, clock: Clock, logger: Logger, ) -> None: self._repository = repository self._runtime = runtime self._clock = clock self._logger = logger def execute(self) -> list[SandboxSession]: now = self._clock.now() expired_sessions = self._repository.list_expired(now) cleaned_sessions: list[SandboxSession] = [] for session in expired_sessions: self._runtime.stop(session.container_id) self._repository.delete(session.session_id) cleaned_sessions.append(session) self._logger.info( 'sandbox_cleaned', attrs={ 'chat_id': session.chat_id, 'session_id': session.session_id, 'container_id': session.container_id, }, ) return cleaned_sessions def _new_session_id() -> str: return uuid4().hex