SPB Git forge

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

52commits 1branches 0releases
11.6 MBsize
maindefault branch
20 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%
2.6 KB · 77 lines python
Raw Blame History
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