New alert service

This commit is contained in:
Urtzi Alfaro
2025-12-05 20:07:01 +01:00
parent 1fe3a73549
commit 667e6e0404
393 changed files with 26002 additions and 61033 deletions

View File

@@ -11,6 +11,7 @@ from typing import Optional
import uuid
import httpx
import structlog
import json
logger = structlog.get_logger()
@@ -40,19 +41,21 @@ DEMO_ALLOWED_OPERATIONS = {
# Limited write operations for realistic testing
"POST": [
"/api/pos/sales",
"/api/pos/sessions",
"/api/orders",
"/api/inventory/adjustments",
"/api/sales",
"/api/production/batches",
"/api/v1/pos/sales",
"/api/v1/pos/sessions",
"/api/v1/orders",
"/api/v1/inventory/adjustments",
"/api/v1/sales",
"/api/v1/production/batches",
"/api/v1/tenants/batch/sales-summary",
"/api/v1/tenants/batch/production-summary",
# Note: Forecast generation is explicitly blocked (see DEMO_BLOCKED_PATHS)
],
"PUT": [
"/api/pos/sales/*",
"/api/orders/*",
"/api/inventory/stock/*",
"/api/v1/pos/sales/*",
"/api/v1/orders/*",
"/api/v1/inventory/stock/*",
],
# Blocked operations
@@ -63,9 +66,9 @@ DEMO_ALLOWED_OPERATIONS = {
# Explicitly blocked paths for demo accounts (even if method would be allowed)
# These require trained AI models which demo tenants don't have
DEMO_BLOCKED_PATHS = [
"/api/forecasts/single",
"/api/forecasts/multi-day",
"/api/forecasts/batch",
"/api/v1/forecasts/single",
"/api/v1/forecasts/multi-day",
"/api/v1/forecasts/batch",
]
DEMO_BLOCKED_PATH_MESSAGE = {
@@ -79,11 +82,59 @@ DEMO_BLOCKED_PATH_MESSAGE = {
class DemoMiddleware(BaseHTTPMiddleware):
"""Middleware to handle demo session logic"""
"""Middleware to handle demo session logic with Redis caching"""
def __init__(self, app, demo_session_url: str = "http://demo-session-service:8000"):
super().__init__(app)
self.demo_session_url = demo_session_url
self._redis_client = None
async def _get_redis_client(self):
"""Get or lazily initialize Redis client"""
if self._redis_client is None:
try:
from shared.redis_utils import get_redis_client
self._redis_client = await get_redis_client()
logger.debug("Demo middleware: Redis client initialized")
except Exception as e:
logger.warning(f"Demo middleware: Failed to get Redis client: {e}. Caching disabled.")
self._redis_client = False # Sentinel value to avoid retrying
return self._redis_client if self._redis_client is not False else None
async def _get_cached_session(self, session_id: str) -> Optional[dict]:
"""Get session info from Redis cache"""
try:
redis_client = await self._get_redis_client()
if not redis_client:
return None
cache_key = f"demo_session:{session_id}"
cached_data = await redis_client.get(cache_key)
if cached_data:
logger.debug("Demo middleware: Cache HIT", session_id=session_id)
return json.loads(cached_data)
else:
logger.debug("Demo middleware: Cache MISS", session_id=session_id)
return None
except Exception as e:
logger.warning(f"Demo middleware: Redis cache read error: {e}")
return None
async def _cache_session(self, session_id: str, session_info: dict, ttl: int = 30):
"""Cache session info in Redis with TTL"""
try:
redis_client = await self._get_redis_client()
if not redis_client:
return
cache_key = f"demo_session:{session_id}"
serialized = json.dumps(session_info)
await redis_client.setex(cache_key, ttl, serialized)
logger.debug(f"Demo middleware: Cached session {session_id} (TTL: {ttl}s)")
except Exception as e:
logger.warning(f"Demo middleware: Redis cache write error: {e}")
async def dispatch(self, request: Request, call_next) -> Response:
"""Process request through demo middleware"""
@@ -113,8 +164,17 @@ class DemoMiddleware(BaseHTTPMiddleware):
# Check if this is a demo session request
if session_id:
try:
# Get session info from demo service
session_info = await self._get_session_info(session_id)
# PERFORMANCE OPTIMIZATION: Check Redis cache first before HTTP call
session_info = await self._get_cached_session(session_id)
if not session_info:
# Cache miss - fetch from demo service
logger.debug("Demo middleware: Fetching from demo service", session_id=session_id)
session_info = await self._get_session_info(session_id)
# Cache the result if successful (30s TTL to balance freshness vs performance)
if session_info:
await self._cache_session(session_id, session_info, ttl=30)
# Accept pending, ready, partial, failed (if data exists), and active (deprecated) statuses
# Even "failed" sessions can be usable if some services succeeded