""" Session management API endpoints Обеспечивает управление сессиями пользователей через Redis: - Верификация существующей сессии - Logout (удаление сессии) """ import json import logging from datetime import datetime, timedelta from typing import Optional, Dict, Any from fastapi import APIRouter, HTTPException from pydantic import BaseModel import redis.asyncio as redis logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/session", tags=["session"]) # Redis connection (используем существующее подключение) redis_client: Optional[redis.Redis] = None def init_redis(redis_conn: redis.Redis): """Initialize Redis connection""" global redis_client redis_client = redis_conn class SessionVerifyRequest(BaseModel): session_token: str class SessionVerifyResponse(BaseModel): success: bool valid: bool unified_id: Optional[str] = None phone: Optional[str] = None contact_id: Optional[str] = None verified_at: Optional[str] = None expires_in_seconds: Optional[int] = None class SessionLogoutRequest(BaseModel): session_token: str class SessionLogoutResponse(BaseModel): success: bool message: str @router.post("/verify", response_model=SessionVerifyResponse) async def verify_session(request: SessionVerifyRequest): """ Проверить валидность сессии по session_token Используется при загрузке страницы, чтобы восстановить сессию пользователя. Если сессия валидна - возвращаем unified_id, phone и другие данные. """ try: if not redis_client: raise HTTPException(status_code=500, detail="Redis connection not initialized") session_key = f"session:{request.session_token}" logger.info(f"🔍 Проверка сессии: {session_key}") # Получаем данные сессии из Redis session_data_raw = await redis_client.get(session_key) if not session_data_raw: logger.info(f"❌ Сессия не найдена или истекла: {session_key}") return SessionVerifyResponse( success=True, valid=False ) # Парсим данные сессии session_data = json.loads(session_data_raw) # Получаем TTL (оставшееся время жизни) ttl = await redis_client.ttl(session_key) logger.info(f"✅ Сессия валидна: unified_id={session_data.get('unified_id')}, TTL={ttl}s") return SessionVerifyResponse( success=True, valid=True, unified_id=session_data.get('unified_id'), phone=session_data.get('phone'), contact_id=session_data.get('contact_id'), verified_at=session_data.get('verified_at'), expires_in_seconds=ttl if ttl > 0 else None ) except json.JSONDecodeError as e: logger.error(f"❌ Ошибка парсинга данных сессии: {e}") return SessionVerifyResponse( success=True, valid=False ) except Exception as e: logger.exception("❌ Ошибка проверки сессии") raise HTTPException(status_code=500, detail=f"Ошибка проверки сессии: {str(e)}") @router.post("/logout", response_model=SessionLogoutResponse) async def logout_session(request: SessionLogoutRequest): """ Выход из сессии (удаление session_token из Redis) Используется при клике на кнопку "Выход". """ try: if not redis_client: raise HTTPException(status_code=500, detail="Redis connection not initialized") session_key = f"session:{request.session_token}" logger.info(f"🚪 Выход из сессии: {session_key}") # Удаляем сессию из Redis deleted = await redis_client.delete(session_key) if deleted > 0: logger.info(f"✅ Сессия удалена: {session_key}") return SessionLogoutResponse( success=True, message="Выход выполнен успешно" ) else: logger.info(f"⚠️ Сессия не найдена (возможно, уже удалена): {session_key}") return SessionLogoutResponse( success=True, message="Сессия уже завершена" ) except Exception as e: logger.exception("❌ Ошибка при выходе из сессии") raise HTTPException(status_code=500, detail=f"Ошибка при выходе: {str(e)}") class SessionCreateRequest(BaseModel): session_token: str unified_id: str phone: str contact_id: str ttl_hours: int = 24 @router.post("/create") async def create_session(request: SessionCreateRequest): """ Создать новую сессию (вызывается после успешной SMS верификации) Обычно вызывается из Step1Phone после получения данных от n8n. """ try: if not redis_client: raise HTTPException(status_code=500, detail="Redis connection not initialized") session_key = f"session:{request.session_token}" session_data = { 'unified_id': request.unified_id, 'phone': request.phone, 'contact_id': request.contact_id, 'verified_at': datetime.utcnow().isoformat(), 'expires_at': (datetime.utcnow() + timedelta(hours=request.ttl_hours)).isoformat() } # Сохраняем в Redis с TTL await redis_client.setex( session_key, request.ttl_hours * 3600, # TTL в секундах json.dumps(session_data) ) logger.info(f"✅ Сессия создана: {session_key}, unified_id={request.unified_id}, TTL={request.ttl_hours}h") return { 'success': True, 'session_token': request.session_token, 'expires_in_seconds': request.ttl_hours * 3600 } except Exception as e: logger.exception("❌ Ошибка создания сессии") raise HTTPException(status_code=500, detail=f"Ошибка создания сессии: {str(e)}")