spb/toit-ka Public
Toit-Ka — louer ou acheter un toit au Québec, un seul endroit (fusion Lou-Ka × Immo-Ka) — www.toit-ka.com
Python 40.2%
TypeScript 39%
CSS 20.2%
HTML 0.7%
1# Author: Simon-Pierre Boucher2# Contact: contact@spboucher.ai3# Project: Toit-Ka4# -----------------------------------------------------------------------------5# hubfav.py : favoris unifiés « Mon univers Ka » — Toit·Ka n'a AUCUN stockage6# local de favoris : le hub Groupe KA (groupe-ka.com) est le magasin central.7# Chaque ♥ est poussé au hub (POST signé, SYNCHRONE) et la liste est relue du8# hub (GET signé, cache mémoire 30 s par ka_id, [] sur toute erreur).9# sig = HMAC-SHA256(KA_SSO_SECRET, "toit-ka.<ka_id>.<ts>") hex (ts ±5 min).10# -----------------------------------------------------------------------------11from __future__ import annotations1213import hashlib14import hmac15import os16import threading17import time1819import requests2021CLIENT_ID = "toit-ka"22KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/")23LIST_TTL = 302425_cache: dict[str, tuple[float, list]] = {}26_lock = threading.Lock()272829def _sig(ka_id: str, ts: int) -> str | None:30 secret = os.environ.get("KA_SSO_SECRET")31 if not secret:32 return None33 return hmac.new(secret.encode(), f"{CLIENT_ID}.{ka_id}.{ts}".encode(),34 hashlib.sha256).hexdigest()353637def hub_toggle(ka_id: str, action: str, item: dict) -> bool:38 """Pousse un ♥ au hub (« add » ou « remove ») — synchrone (action utilisateur)."""39 if not ka_id or not str(ka_id).startswith("ka-"):40 return False41 ts = int(time.time())42 sig = _sig(ka_id, ts)43 if not sig:44 return False45 try:46 r = requests.post(f"{KA_HUB_URL}/api/sso/favorites", timeout=6, json={47 "client_id": CLIENT_ID, "ka_id": ka_id,48 "ts": str(ts), "sig": sig, "action": action, "item": item,49 })50 return r.status_code == 20051 except Exception:52 return False535455def hub_list(ka_id: str) -> list:56 """Favoris toit-ka du membre, lus du hub (cache 30 s, [] sur erreur)."""57 if not ka_id:58 return []59 now = time.time()60 with _lock:61 hit = _cache.get(ka_id)62 if hit and now - hit[0] < LIST_TTL:63 return hit[1]64 ts = int(now)65 sig = _sig(ka_id, ts)66 if not sig:67 return []68 try:69 r = requests.get(70 f"{KA_HUB_URL}/api/sso/favorites",71 params={"client_id": CLIENT_ID, "ka_id": ka_id, "ts": ts, "sig": sig},72 timeout=5)73 if r.status_code != 200:74 return []75 favs = r.json().get("favorites")76 if not isinstance(favs, list):77 return []78 except Exception:79 return []80 with _lock:81 _cache[ka_id] = (now, favs)82 return favs838485def invalidate(ka_id: str) -> None:86 """Invalide le cache après un toggle (état frais au prochain GET)."""87 with _lock:88 _cache.pop(ka_id, None)89