# ----------------------------------------------------------------------------- # Ora-Ka — Plateforme unifiée des agrégateurs Ka # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # hubfav.py : « Mon univers Ka » — lecture des favoris unifiés du membre depuis # le magasin central du hub Groupe KA (toutes plateformes : Lou·Ka, Immo·Ka, # Auto·Ka, Fabri·Ka, Food·Ka…). GET signé HMAC du secret SSO partagé, # scope=all. Cache mémoire 30 s par ka_id ; toute erreur → liste vide # (l'affichage est un bonus, jamais bloquant). # Config .env : KA_SSO_SECRET, KA_HUB_URL (optionnel). # ----------------------------------------------------------------------------- from __future__ import annotations import hashlib import hmac import os import threading import time import requests CLIENT_ID = "ora-ka" KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/") CACHE_TTL = 30.0 # secondes — le hub reste la source de vérité _cache: dict[str, tuple[float, list]] = {} _cache_lock = threading.Lock() def _sig(ka_id: str, ts: int) -> str | None: secret = os.environ.get("KA_SSO_SECRET") if not secret: return None return hmac.new(secret.encode(), f"{CLIENT_ID}.{ka_id}.{ts}".encode(), hashlib.sha256).hexdigest() def hub_list_all(ka_id: str | None) -> list: """Tous les favoris du membre (toutes plateformes du groupe), ou [].""" if not ka_id or not str(ka_id).startswith("ka-"): return [] # compte legacy non relié au hub ka_id = str(ka_id) now = time.time() with _cache_lock: hit = _cache.get(ka_id) if hit and now - hit[0] < CACHE_TTL: return hit[1] ts = int(now) sig = _sig(ka_id, ts) if not sig: return [] try: r = requests.get(f"{KA_HUB_URL}/api/sso/favorites", timeout=5, params={ "client_id": CLIENT_ID, "ka_id": ka_id, "ts": str(ts), "sig": sig, "scope": "all", }) if not r.ok: return [] favs = r.json().get("favorites") or [] if not isinstance(favs, list): return [] except Exception: return [] # best-effort : la landing s'affiche simplement sans la section with _cache_lock: _cache[ka_id] = (now, favs) return favs