Files
bakery-ia/services/demo_session/app/api/routes.py
2025-10-03 14:09:34 +02:00

255 lines
8.0 KiB
Python

"""
Demo Session API Routes
"""
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
import structlog
from app.api.schemas import (
DemoSessionCreate,
DemoSessionResponse,
DemoSessionExtend,
DemoSessionDestroy,
DemoSessionStats,
DemoAccountInfo
)
from app.services import DemoSessionManager, DemoDataCloner, DemoCleanupService
from app.core import get_db, get_redis, settings, RedisClient
logger = structlog.get_logger()
router = APIRouter(prefix="/api/demo", tags=["demo"])
@router.get("/accounts", response_model=List[DemoAccountInfo])
async def get_demo_accounts():
"""
Get public demo account information
Returns credentials for prospects to use
"""
accounts = []
for account_type, config in settings.DEMO_ACCOUNTS.items():
accounts.append({
"account_type": account_type,
"name": config["name"],
"email": config["email"],
"password": "DemoSanPablo2024!" if "sanpablo" in config["email"] else "DemoLaEspiga2024!",
"description": (
"Panadería individual que produce todo localmente"
if account_type == "individual_bakery"
else "Punto de venta con obrador central"
),
"features": (
["Gestión de Producción", "Recetas", "Inventario", "Previsión de Demanda", "Ventas"]
if account_type == "individual_bakery"
else ["Gestión de Proveedores", "Inventario", "Ventas", "Pedidos", "Previsión"]
),
"business_model": (
"Producción Local" if account_type == "individual_bakery" else "Obrador Central + Punto de Venta"
)
})
return accounts
@router.post("/session/create", response_model=DemoSessionResponse)
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
"""
logger.info("Creating demo session", demo_account_type=request.demo_account_type)
try:
# Get client info
ip_address = request.ip_address or http_request.client.host
user_agent = request.user_agent or http_request.headers.get("user-agent", "")
# Create session
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
)
# Clone demo data using Kubernetes Job (better architecture)
from app.services.k8s_job_cloner import K8sJobCloner
job_cloner = K8sJobCloner()
# Trigger async cloning job (don't wait for completion)
import asyncio
asyncio.create_task(
job_cloner.clone_tenant_data(
session.session_id,
"", # base_tenant_id not used in job approach
str(session.virtual_tenant_id),
request.demo_account_type
)
)
# Mark as data cloning started
await session_manager.mark_data_cloned(session.session_id)
await session_manager.mark_redis_populated(session.session_id)
# Generate session token (simple JWT-like format)
import jwt
from datetime import datetime, timezone
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", # In production, use proper secret
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.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.post("/session/extend", response_model=DemoSessionResponse)
async def extend_demo_session(
request: DemoSessionExtend,
db: AsyncSession = Depends(get_db),
redis: RedisClient = Depends(get_redis)
):
"""
Extend demo session expiration
"""
try:
session_manager = DemoSessionManager(db, redis)
session = await session_manager.extend_session(request.session_id)
# Generate new token
import jwt
session_token = jwt.encode(
{
"session_id": session.session_id,
"virtual_tenant_id": str(session.virtual_tenant_id),
"demo_account_type": session.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.metadata.get("demo_config", {}),
"session_token": session_token
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error("Failed to extend session", error=str(e))
raise HTTPException(status_code=500, detail=str(e))
@router.post("/session/destroy")
async def destroy_demo_session(
request: DemoSessionDestroy,
db: AsyncSession = Depends(get_db),
redis: RedisClient = Depends(get_redis)
):
"""
Destroy demo session and cleanup resources
"""
try:
session_manager = DemoSessionManager(db, redis)
await session_manager.destroy_session(request.session_id)
return {"message": "Session destroyed successfully", "session_id": request.session_id}
except Exception as e:
logger.error("Failed to destroy session", error=str(e))
raise HTTPException(status_code=500, detail=str(e))
@router.get("/session/{session_id}")
async def get_session_info(
session_id: str,
db: AsyncSession = Depends(get_db),
redis: RedisClient = Depends(get_redis)
):
"""
Get demo session information
"""
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.get("/stats", response_model=DemoSessionStats)
async def get_demo_stats(
db: AsyncSession = Depends(get_db),
redis: RedisClient = Depends(get_redis)
):
"""
Get demo session statistics
"""
session_manager = DemoSessionManager(db, redis)
stats = await session_manager.get_session_stats()
return stats
@router.post("/cleanup/run")
async def run_cleanup(
db: AsyncSession = Depends(get_db),
redis: RedisClient = Depends(get_redis)
):
"""
Manually trigger session cleanup
Internal endpoint for CronJob
"""
cleanup_service = DemoCleanupService(db, redis)
stats = await cleanup_service.cleanup_expired_sessions()
return stats
@router.get("/health")
async def health_check(redis: RedisClient = Depends(get_redis)):
"""
Health check endpoint
"""
redis_ok = await redis.ping()
return {
"status": "healthy" if redis_ok else "degraded",
"redis": "connected" if redis_ok else "disconnected"
}