2025-10-06 15:27:01 +02:00
|
|
|
"""
|
|
|
|
|
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,
|
2025-10-07 07:15:07 +02:00
|
|
|
"demo_config": session.session_metadata.get("demo_config", {}),
|
2025-10-06 15:27:01 +02:00
|
|
|
"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))
|