import json from datetime import datetime, timedelta import pytz from typing import Optional, List, Dict, Any import logging from utils.logging_utils import log_to_file from utils.cache_manager import CacheManager from utils.admin import supabase class AuthenticationCache: def __init__(self): self.cache_manager = CacheManager() # initialise la connexion Redis self.cache_duration = timedelta(hours=24) # Duree de validité du cache (24h) self.cache_version_key = "auth_cache_version" # Pour suivre la version du cache self.batch_size = 2 # Taille du batch pour le chargement par lots self.session_cache_key = "user_session:" self.history_cache_key = "chat_history:" # Ajout de cet attribut async def is_cache_initialized(self) -> bool: """Vérifie si le cache est déjà initialisé""" try: all_keys = self.cache_manager.redis_client.keys("auth_user:*") return len(all_keys) > 0 except Exception as e: log_to_file(f"Erreur lors de la vérification du cache : {str(e)}", level=logging.ERROR) return False async def get_cache_version(self) -> str: """Récupère la version actuelle du cache""" try: return await self.cache_manager.get(self.cache_version_key) or "" except Exception as e: log_to_file(f"Erreur lors de la récupération de la version du cache : {str(e)}", level=logging.ERROR) return "" async def update_cache_version(self) -> None: """Met à jour la version du cache""" try: current_time = datetime.now(pytz.UTC).isoformat() await self.cache_manager.set(self.cache_version_key, current_time, expiry=self.cache_duration) except Exception as e: log_to_file(f"Erreur lors de la mise à jour de la version du cache : {str(e)}", level=logging.ERROR) async def is_cache_outdated(self) -> bool: """Vérifie si le cache doit être mis à jour""" try: # Vérifier la dernière mise à jour du cache cache_version = await self.get_cache_version() if not cache_version: return True # Vérifier les modifications récentes dans Supabase last_update = datetime.fromisoformat(cache_version) recent_changes = supabase.table("users").select("updated_at").gt( "updated_at", last_update.isoformat() ).execute() return bool(recent_changes.data) except Exception as e: log_to_file(f"Erreur lors de la vérification de l'état du cache : {str(e)}", level=logging.ERROR) return True async def get_user_from_cache(self, email: str) -> Optional[Dict[str, Any]]: """Récupère les informations utilisateur depuis le cache. Args: email: L'adresse email de l'utilisateur à rechercher dans le cache. Returns: Un dictionnaire contenant les informations de l'utilisateur si trouvé dans le cache, None sinon. """ try: # Construit la clé de cache en utilisant l'email. cache_key = f"auth_user:{email}" # Récupère l'utilisateur depuis le cache en utilisant la clé. return await self.cache_manager.get(cache_key) except Exception as e: # Enregistre une erreur si une exception se produit pendant la récupération du cache. log_to_file(f"Erreur lors de la récupération du cache utilisateur : {str(e)}", level=logging.ERROR) # Retourne None en cas d'erreur. return None async def set_user_in_cache(self, user_data: Dict[str, Any]) -> bool: """Stocke les informations utilisateur dans le cache""" try: cache_key = f"auth_user:{user_data['email']}" return await self.cache_manager.set(cache_key, user_data, expiry=self.cache_duration) except Exception as e: log_to_file(f"Erreur lors du stockage en cache : {str(e)}", level=logging.ERROR) return False async def load_all_users_to_cache(self) -> bool: try: log_to_file("Début du chargement des utilisateurs dans Redis", level=logging.INFO) # Récupérer le nombre total d'utilisateurs total_users = supabase.table("users").select("id", count="exact").execute() total_count = total_users.count log_to_file(f"Nombre total d'utilisateurs : {total_count}", level=logging.INFO) # Charger les utilisateurs par lots loaded_count = 0 for offset in range(0, total_count, self.batch_size): log_to_file(f"Chargement du lot {offset} - {offset + self.batch_size - 1}", level=logging.INFO) users = supabase.table("users").select("*").range(offset, offset + self.batch_size - 1).execute() if users.data: for user in users.data: if await self.set_user_in_cache(user): loaded_count += 1 log_to_file(f"Utilisateur {user['email']} mis en cache", level=logging.INFO) else: log_to_file(f"Échec du chargement de l'utilisateur : {user['email']}", level=logging.ERROR) else: log_to_file(f"Aucun utilisateur trouvé dans la plage {offset} - {offset + self.batch_size - 1}", level=logging.WARNING) log_to_file(f"Chargement terminé : {loaded_count}/{total_count} utilisateurs mis en cache", level=logging.INFO) # Mettre à jour la version du cache await self.update_cache_version() # Debug await self.debug_redis_content() return True except Exception as e: log_to_file(f"Erreur lors du chargement des utilisateurs : {str(e)}", level=logging.ERROR) return False async def sync_cache_with_db(self) -> bool: """Synchronise le cache avec la base de données""" try: cache_version = await self.get_cache_version() if not cache_version: return await self.load_all_users_to_cache() last_update = datetime.fromisoformat(cache_version) updated_users = supabase.table("users").select("*").gt( "updated_at", last_update.isoformat() ).execute() if updated_users.data: for user in updated_users.data: await self.set_user_in_cache(user) await self.update_cache_version() log_to_file(f"Cache mis à jour avec {len(updated_users.data)} modifications", level=logging.INFO) return True except Exception as e: log_to_file(f"Erreur lors de la synchronisation du cache : {str(e)}", level=logging.ERROR) return False async def load_user_sessions_to_cache(self) -> bool: """Charge toutes les sessions actives dans Redis""" try: log_to_file("Chargement des sessions utilisateurs dans Redis", level=logging.INFO) # Récupérer toutes les sessions actives sessions = supabase.table("chat_sessions").select("*").eq( "is_active", True ).execute() if not sessions.data: log_to_file("Aucune session active trouvée", level=logging.INFO) return True # Charger chaque session dans Redis for session in sessions.data: session_key = f"{self.session_cache_key}{session['user_id']}" await self.cache_manager.set( session_key, session, expiry=self.cache_duration ) # Charger l'historique associé history = supabase.table("messages").select("*").eq( "session_id", session['session_id'] ).order('created_at', desc=False).execute() if history.data: history_key = f"{self.history_cache_key}{session['session_id']}" await self.cache_manager.set( history_key, history.data, expiry=self.cache_duration ) log_to_file(f"Sessions et historiques chargés : {len(sessions.data)} sessions", level=logging.INFO) return True except Exception as e: log_to_file(f"Erreur lors du chargement des sessions : {str(e)}", level=logging.ERROR) return False async def get_active_session(self, user_id: int) -> Optional[Dict]: """Récupère la session active d'un utilisateur depuis le cache""" try: session_key = f"{self.session_cache_key}{user_id}" session = await self.cache_manager.get(session_key) if not session: # Si pas dans le cache, chercher dans Supabase et mettre en cache db_session = supabase.table("chat_sessions").select("*").eq( "user_id", user_id ).eq("is_active", True).execute() if db_session.data: session = db_session.data[0] await self.cache_manager.set( session_key, session, expiry=self.cache_duration ) return session except Exception as e: log_to_file(f"Erreur lors de la récupération de la session : {str(e)}", level=logging.ERROR) return None async def get_chat_history(self, session_id: str) -> List[Dict]: """Récupère l'historique du chat depuis le cache""" try: history_key = f"{self.history_cache_key}{session_id}" history = await self.cache_manager.get(history_key) if not history: # Si pas dans le cache, chercher dans Supabase et mettre en cache db_history = supabase.table("messages").select("*").eq( "session_id", session_id ).order('created_at', desc=False).execute() if db_history.data: history = db_history.data await self.cache_manager.set( history_key, history, expiry=self.cache_duration ) return history or [] except Exception as e: log_to_file(f"Erreur lors de la récupération de l'historique : {str(e)}", level=logging.ERROR) return [] async def update_session_and_history(self, session_data: Dict, new_message: Dict = None): """Met à jour la session et l'historique dans le cache""" try: session_key = f"{self.session_cache_key}{session_data['user_id']}" await self.cache_manager.set( session_key, session_data, expiry=self.cache_duration ) if new_message: history_key = f"{self.history_cache_key}{session_data['session_id']}" current_history = await self.get_chat_history(session_data['session_id']) current_history.append(new_message) await self.cache_manager.set( history_key, current_history, expiry=self.cache_duration ) except Exception as e: log_to_file(f"Erreur lors de la mise à jour de la session : {str(e)}", level=logging.ERROR) async def debug_redis_content(self): """Affiche le contenu de Redis pour debug""" try: all_keys = self.cache_manager.redis_client.keys("auth_user:*") log_to_file(f"Nombre d'utilisateurs en cache : {len(all_keys)}", level=logging.INFO) for key in all_keys: log_to_file(f"Utilisateur en cache : {key}", level=logging.INFO) except Exception as e: log_to_file(f"Erreur debug Redis : {str(e)}", level=logging.ERROR)