""" Demo Sessions API - Atomic CRUD operations on DemoSession model """ from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request from typing import Optional from uuid import UUID import structlog import jwt from app.api.schemas import DemoSessionCreate, DemoSessionResponse from app.services import DemoSessionManager from app.core import get_db, get_redis, RedisClient from sqlalchemy.ext.asyncio import AsyncSession from shared.routing import RouteBuilder router = APIRouter(tags=["demo-sessions"]) logger = structlog.get_logger() route_builder = RouteBuilder('demo') @router.post( route_builder.build_base_route("sessions", include_tenant_prefix=False), response_model=DemoSessionResponse, status_code=201 ) async def create_demo_session( request: DemoSessionCreate, http_request: Request, db: AsyncSession = Depends(get_db), redis: RedisClient = Depends(get_redis) ): """Create a new isolated demo session (ATOMIC)""" logger.info("Creating demo session", demo_account_type=request.demo_account_type) try: ip_address = request.ip_address or http_request.client.host user_agent = request.user_agent or http_request.headers.get("user-agent", "") session_manager = DemoSessionManager(db, redis) session = await session_manager.create_session( demo_account_type=request.demo_account_type, user_id=request.user_id, ip_address=ip_address, user_agent=user_agent ) # Trigger async data cloning job from app.services.k8s_job_cloner import K8sJobCloner import asyncio job_cloner = K8sJobCloner() asyncio.create_task( job_cloner.clone_tenant_data( session.session_id, "", str(session.virtual_tenant_id), request.demo_account_type ) ) await session_manager.mark_data_cloned(session.session_id) await session_manager.mark_redis_populated(session.session_id) # Generate session token session_token = jwt.encode( { "session_id": session.session_id, "virtual_tenant_id": str(session.virtual_tenant_id), "demo_account_type": request.demo_account_type, "exp": session.expires_at.timestamp() }, "demo-secret-key", algorithm="HS256" ) return { "session_id": session.session_id, "virtual_tenant_id": str(session.virtual_tenant_id), "demo_account_type": session.demo_account_type, "status": session.status.value, "created_at": session.created_at, "expires_at": session.expires_at, "demo_config": session.session_metadata.get("demo_config", {}), "session_token": session_token } except Exception as e: logger.error("Failed to create demo session", error=str(e)) raise HTTPException(status_code=500, detail=f"Failed to create demo session: {str(e)}") @router.get( route_builder.build_resource_detail_route("sessions", "session_id", include_tenant_prefix=False), response_model=dict ) async def get_session_info( session_id: str = Path(...), db: AsyncSession = Depends(get_db), redis: RedisClient = Depends(get_redis) ): """Get demo session information (ATOMIC READ)""" session_manager = DemoSessionManager(db, redis) session = await session_manager.get_session(session_id) if not session: raise HTTPException(status_code=404, detail="Session not found") return session.to_dict() @router.delete( route_builder.build_resource_detail_route("sessions", "session_id", include_tenant_prefix=False), response_model=dict ) async def destroy_demo_session( session_id: str = Path(...), db: AsyncSession = Depends(get_db), redis: RedisClient = Depends(get_redis) ): """Destroy demo session and cleanup resources (ATOMIC DELETE)""" try: session_manager = DemoSessionManager(db, redis) await session_manager.destroy_session(session_id) return {"message": "Session destroyed successfully", "session_id": session_id} except Exception as e: logger.error("Failed to destroy session", error=str(e)) raise HTTPException(status_code=500, detail=str(e))