Spaces:
Running
Running
Komalpreet Kaur
feat: implement responsive, theme-adaptive visitor analytics modal dialog box
ccfe044 | import redis | |
| from app.core.config import settings | |
| import logging | |
| import time | |
| logger = logging.getLogger("soma.analytics") | |
| class AnalyticsManager: | |
| def __init__(self): | |
| self._client = None | |
| self._connected = False | |
| self._last_attempt = 0.0 | |
| self._cooldown = 30.0 # seconds to wait before trying to reconnect if offline | |
| def get_client(self): | |
| # Return existing connection if still healthy | |
| if self._client is not None: | |
| try: | |
| self._client.ping() | |
| self._connected = True | |
| return self._client | |
| except Exception: | |
| logger.warning("Redis connection lost, re-establishing...") | |
| self._client = None | |
| self._connected = False | |
| # If we failed to connect recently, don't block the request; return None (fallback) fast. | |
| now = time.time() | |
| if now - self._last_attempt < self._cooldown: | |
| return None | |
| self._last_attempt = now | |
| # 1. Try REDIS_URL first (Upstash URL format) | |
| if settings.REDIS_URL: | |
| try: | |
| print(f"Analytics: Connecting to Upstash Redis...") | |
| # Note: socket_timeout/socket_connect_timeout are small to prevent FastAPI from hanging | |
| self._client = redis.Redis.from_url( | |
| settings.REDIS_URL, | |
| decode_responses=True, | |
| socket_timeout=3.0, | |
| socket_connect_timeout=3.0, | |
| retry_on_timeout=False | |
| ) | |
| self._client.ping() | |
| self._connected = True | |
| print("Analytics: Connected to Upstash Redis successfully!") | |
| return self._client | |
| except Exception as e: | |
| print(f"Analytics: Failed to connect to Upstash via REDIS_URL: {e}") | |
| self._client = None | |
| self._connected = False | |
| # 2. Try default REDIS_HOST / REDIS_PORT (Local fallback) | |
| if settings.REDIS_HOST: | |
| try: | |
| print(f"Analytics: Connecting to local Redis at {settings.REDIS_HOST}:{settings.REDIS_PORT}...") | |
| self._client = redis.Redis( | |
| host=settings.REDIS_HOST, | |
| port=settings.REDIS_PORT, | |
| decode_responses=True, | |
| socket_timeout=2.0, | |
| socket_connect_timeout=2.0, | |
| retry_on_timeout=False | |
| ) | |
| self._client.ping() | |
| self._connected = True | |
| print("Analytics: Connected to local Redis successfully!") | |
| return self._client | |
| except Exception as e: | |
| print(f"Analytics: Failed to connect to local Redis: {e}") | |
| self._client = None | |
| self._connected = False | |
| return None | |
| def record_hit(self, visitor_id: str) -> bool: | |
| """ | |
| Record a visit hit. | |
| Increments the total visits count and adds the visitor_id to the unique set. | |
| Optimized for Upstash Free Tier limits by being called exactly once per session. | |
| """ | |
| client = self.get_client() | |
| if client is None: | |
| print("Analytics: Redis is offline, skipping hit.") | |
| return False | |
| try: | |
| # 1. Increment total visits | |
| client.incr("soma:analytics:total_visits") | |
| # 2. Add visitor_id to unique visitors set | |
| client.sadd("soma:analytics:unique_visitors", visitor_id) | |
| return True | |
| except Exception as e: | |
| print(f"Analytics: Failed to record hit in Redis: {e}") | |
| return False | |
| def get_stats(self) -> dict: | |
| """ | |
| Retrieve stats (total visitors, unique visitors) from Redis. | |
| Returns mock/offline status if Redis connection fails, ensuring zero crashes. | |
| """ | |
| client = self.get_client() | |
| if client is None or not self._connected: | |
| return { | |
| "total_visitors": 0, | |
| "unique_visitors": 0, | |
| "status": "offline" | |
| } | |
| try: | |
| # Fetch total visits | |
| total_val = client.get("soma:analytics:total_visits") | |
| total = int(total_val) if total_val else 0 | |
| # Fetch cardinality of unique set | |
| unique = client.scard("soma:analytics:unique_visitors") or 0 | |
| return { | |
| "total_visitors": total, | |
| "unique_visitors": unique, | |
| "status": "online" | |
| } | |
| except Exception as e: | |
| print(f"Analytics: Failed to query stats: {e}") | |
| return { | |
| "total_visitors": 0, | |
| "unique_visitors": 0, | |
| "status": "error", | |
| "detail": str(e) | |
| } | |
| analytics_manager = AnalyticsManager() | |