Python 67%
TypeScript 18.2%
CSS 14.4%
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# favorites.py : routes API des favoris (♥) — session Groupe KA requise.5# AUCUN stockage local : lecture et écriture passent par le hub Groupe KA6# (magasin central « Mon univers Ka »), voir immoka/hubfav.py.7# GET /api/favorites -> {ids:[item_id…], items:[…]} (depuis le hub)8# POST /api/favorites/toggle {on:bool, item:{item_id,title,…}} -> {ok,on}9# 401 si pas de session, ou si le compte n'est pas relié au hub (pas de10# ka_id) — le frontend redirige alors vers /api/auth/ka/login.11# -----------------------------------------------------------------------------12from __future__ import annotations1314from fastapi import APIRouter, Request15from fastapi.responses import JSONResponse1617from .auth import current_user18from .hubfav import hub_list, hub_toggle, invalidate1920router = APIRouter(prefix="/api/favorites")2122# champs acceptés d'un item de favori (et longueur maximale)23_ITEM_FIELDS = {"item_id": 120, "title": 200, "subtitle": 200,24 "price_label": 60, "image_url": 500, "url": 500}252627def _ka_id(request: Request) -> str | None:28 """KA-ID de la session, ou None (pas de session / compte non relié)."""29 user = current_user(request)30 if not user:31 return None32 ka_id = str(user.get("ka_id") or "")33 return ka_id if ka_id.startswith("ka-") else None343536@router.get("")37def list_favorites(request: Request):38 """Favoris immo-ka du membre connecté, lus du hub Groupe KA."""39 ka_id = _ka_id(request)40 if not ka_id:41 return JSONResponse({"error": "non connecté"}, status_code=401)42 items = hub_list(ka_id)43 return {"ids": [it.get("item_id") for it in items if it.get("item_id")],44 "items": items}454647@router.post("/toggle")48async def toggle_favorite(request: Request):49 """Ajoute (on=true) ou retire (on=false) un favori — poussé au hub."""50 ka_id = _ka_id(request)51 if not ka_id:52 return JSONResponse({"error": "non connecté"}, status_code=401)53 try:54 body = await request.json()55 assert isinstance(body, dict)56 except Exception:57 return JSONResponse({"error": "corps JSON attendu"}, status_code=400)58 on = bool(body.get("on"))59 raw = body.get("item") or {}60 item = {k: str(raw.get(k) or "")[:n] for k, n in _ITEM_FIELDS.items()}61 if not item["item_id"]:62 return JSONResponse({"error": "item.item_id requis"}, status_code=400)63 ok = hub_toggle(ka_id, "add" if on else "remove", item)64 # signal fort du moteur de préférences KA ID (features lues de la BD)65 from . import db as _db, kaid as _kaid66 con = _db.connect()67 row = con.execute("SELECT * FROM listings WHERE uid=?",68 (item["item_id"],)).fetchone()69 con.close()70 from .web import _kaid_features as _feats71 _kaid.track({"ka_id": ka_id}, "favorite" if on else "unfavorite",72 entity_type="property", entity_id=item["item_id"],73 features=_feats(dict(row)) if row else None)74 invalidate(ka_id) # le prochain GET relit l'état frais du hub75 return {"ok": ok, "on": on}76