SPB Git forge

spb/vrai-prix

Public

Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.

60commits 1branches 0releases
12.3 MBsize
maindefault branch
17 days agolast push
TypeScript 90.2% JavaScript 3.5% Python 3.4% CSS 1.9% HTML 0.6%
3.4 KB · 95 lines typescript
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// -----------------------------------------------------------------------------3// Profil membre lu depuis le HUB Groupe KA (groupe-ka.com).4//   Le hub est LA source de vérité du profil (bio, ville, emploi, entreprise,5//   site web, réseaux sociaux, photo, statut, profil public) — l'édition se6//   fait sur groupe-ka.com/compte, Vrai-Prix ne fait qu'afficher.7//   GET {hub}/api/sso/profile?client_id=vrai-prix&ka_id=…&ts=…&sig=…8//   avec sig = HMAC-SHA256(KA_SSO_SECRET, "vrai-prix.<ka_id>.<ts>") en hex9//   (même secret que le SSO). Cache mémoire 60 s (les 404 aussi — cache10//   négatif) ; null sur toute erreur (réseau, 401, 5xx, vieux compte non11//   relié) → l'appelant retombe sur les données locales de session.12//   Aucune dépendance : node:crypto + fetch natif seulement.13// -----------------------------------------------------------------------------14import { createHmac } from "node:crypto";15import { CLIENT_ID, KA_HUB_URL } from "@/lib/ka-auth";1617const CACHE_TTL_MS = 60_000; // 60 s18const TIMEOUT_MS = 5_000; // 5 s1920export type HubSocials = {21  instagram?: string;22  facebook?: string;23  x?: string;24  linkedin?: string;25  tiktok?: string;26  youtube?: string;27};2829/** Réponse de GET /api/sso/profile du hub (200). */30export type HubProfile = {31  ka_id: string;32  name: string;33  email: string;34  picture: string;35  role: string;36  role_label: string;37  bio: string;38  city: string;39  phone: string;40  website: string;41  job_title: string;42  company: string;43  birth_date: string;44  age: number | null;45  socials: HubSocials;46  public: boolean;47  public_url: string;48  created_at: string | number;49};5051// Cache mémoire process-wide (Map globale — survit au HMR en dev) :52// 200 → profil ; 404 → null (cache négatif). Erreurs transitoires : pas de cache.53type CacheEntry = { at: number; data: HubProfile | null };54const g = globalThis as unknown as { __vpHubProfileCache?: Map<string, CacheEntry> };55const cache: Map<string, CacheEntry> =56  g.__vpHubProfileCache ?? (g.__vpHubProfileCache = new Map());5758/** Profil du membre au hub Groupe KA, ou null (inconnu ou injoignable). */59export async function fetchHubProfile(kaId: string): Promise<HubProfile | null> {60  const secret = process.env.KA_SSO_SECRET;61  if (!secret || !kaId) return null;6263  const now = Date.now();64  const hit = cache.get(kaId);65  if (hit && now - hit.at < CACHE_TTL_MS) return hit.data;6667  const ts = Math.floor(now / 1000);68  const sig = createHmac("sha256", secret)69    .update(`${CLIENT_ID}.${kaId}.${ts}`)70    .digest("hex");71  const url =72    `${KA_HUB_URL}/api/sso/profile?client_id=${encodeURIComponent(CLIENT_ID)}` +73    `&ka_id=${encodeURIComponent(kaId)}&ts=${ts}&sig=${sig}`;7475  const ctrl = new AbortController();76  const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);77  try {78    const r = await fetch(url, { signal: ctrl.signal, cache: "no-store" });79    if (r.status === 404) {80      cache.set(kaId, { at: now, data: null }); // cache négatif 60 s81      return null;82    }83    if (!r.ok) return null; // transitoire (401, 5xx…) : pas de cache84    const data = (await r.json()) as unknown;85    if (typeof data !== "object" || data === null) return null;86    const profile = data as HubProfile;87    cache.set(kaId, { at: now, data: profile });88    return profile;89  } catch {90    return null; // réseau / timeout / JSON : pas de cache91  } finally {92    clearTimeout(timer);93  }94}95