Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)
Python 69.3%
TypeScript 16.7%
CSS 7.9%
JavaScript 4.7%
HTML 1.4%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: restoka/hubprofile.py4# Desc: Profil membre lu depuis le HUB Groupe KA (groupe-ka.com) — le hub est5# LA source de vérité du profil (l'édition se fait sur6# groupe-ka.com/compte, Resto·Ka ne fait qu'afficher).7# GET {hub}/api/sso/profile?client_id=resto-ka&ka_id=…&ts=…&sig=…8# avec sig = HMAC-SHA256(KA_SSO_SECRET, "resto-ka.<ka_id>.<ts>") en hex.9# Cache mémoire 60 s ; None sur toute erreur. Calqué louka/hubprofile.py.10# ==============================================================================11from __future__ import annotations1213import hashlib14import hmac15import os16import threading17import time18from datetime import datetime, timezone1920import requests2122KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/")23CLIENT_ID = "resto-ka"24CACHE_TTL = 6025TIMEOUT = 52627_cache: dict[str, tuple[float, dict | None]] = {}28_lock = threading.Lock()293031def fetch_hub_profile(ka_id: str) -> dict | None:32 """Profil du membre au hub Groupe KA, ou None (inconnu ou injoignable)."""33 secret = os.environ.get("KA_SSO_SECRET")34 if not secret or not ka_id:35 return None36 now = time.time()37 with _lock:38 hit = _cache.get(ka_id)39 if hit and now - hit[0] < CACHE_TTL:40 return hit[1]41 data: dict | None = None42 try:43 ts = int(now)44 sig = hmac.new(secret.encode(), f"{CLIENT_ID}.{ka_id}.{ts}".encode(),45 hashlib.sha256).hexdigest()46 r = requests.get(47 f"{KA_HUB_URL}/api/sso/profile",48 params={"client_id": CLIENT_ID, "ka_id": ka_id,49 "ts": ts, "sig": sig},50 timeout=TIMEOUT)51 if r.status_code == 200:52 data = r.json()53 if not isinstance(data, dict):54 return None55 elif r.status_code != 404:56 return None # erreur transitoire : pas de cache57 except Exception:58 return None59 with _lock:60 _cache[ka_id] = (now, data)61 return data626364def to_epoch(v) -> float | None:65 """created_at du hub (epoch OU chaîne ISO) -> epoch secondes, sinon None."""66 if isinstance(v, (int, float)):67 return float(v)68 if isinstance(v, str) and v:69 try:70 dt = datetime.fromisoformat(v.replace("Z", "+00:00"))71 if dt.tzinfo is None:72 dt = dt.replace(tzinfo=timezone.utc)73 return dt.timestamp()74 except ValueError:75 return None76 return None77