# Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai # Project: Toit-Ka # ----------------------------------------------------------------------------- # hubprofile.py : profil membre lu depuis le HUB Groupe KA (groupe-ka.com). # Le hub est LA source de vérité (bio, ville, emploi, réseaux, photo, statut) # — l'édition se fait sur groupe-ka.com/compte, Toit·Ka ne fait qu'afficher. # GET {hub}/api/sso/profile?client_id=toit-ka&ka_id=…&ts=…&sig=… # avec sig = HMAC-SHA256(KA_SSO_SECRET, "toit-ka..") en hex. # Cache mémoire 60 s ; None sur toute erreur -> repli sur les données locales. # ----------------------------------------------------------------------------- from __future__ import annotations import hashlib import hmac import os import threading import time from datetime import datetime, timezone import requests KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/") CLIENT_ID = "toit-ka" CACHE_TTL = 60 TIMEOUT = 5 _cache: dict[str, tuple[float, dict | None]] = {} _lock = threading.Lock() def fetch_hub_profile(ka_id: str) -> dict | None: """Profil du membre au hub Groupe KA, ou None (inconnu ou injoignable).""" secret = os.environ.get("KA_SSO_SECRET") if not secret or not ka_id: return None now = time.time() with _lock: hit = _cache.get(ka_id) if hit and now - hit[0] < CACHE_TTL: return hit[1] data: dict | None = None try: ts = int(now) sig = hmac.new(secret.encode(), f"{CLIENT_ID}.{ka_id}.{ts}".encode(), hashlib.sha256).hexdigest() r = requests.get( f"{KA_HUB_URL}/api/sso/profile", params={"client_id": CLIENT_ID, "ka_id": ka_id, "ts": ts, "sig": sig}, timeout=TIMEOUT) if r.status_code == 200: data = r.json() if not isinstance(data, dict): return None elif r.status_code != 404: return None # erreur transitoire (401, 5xx…) : pas de cache except Exception: return None # réseau/JSON : pas de cache with _lock: _cache[ka_id] = (now, data) # 200 -> data ; 404 -> None (négatif) return data def to_epoch(v) -> float | None: """created_at du hub (epoch OU chaîne ISO, UTC si naïve) -> epoch secondes.""" if isinstance(v, (int, float)): return float(v) if isinstance(v, str) and v: try: dt = datetime.fromisoformat(v.replace("Z", "+00:00")) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.timestamp() except ValueError: return None return None