API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com
Python 60.9%
HTML 21%
TypeScript 7.3%
JavaScript 5.2%
CSS 4.8%
Shell 0.8%
1# ============================================2# Projet : API-KA3# Fichier : src/api/main.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-178# ============================================9"""Application FastAPI d'API-KA — servie par uvicorn sur 127.0.0.1:8000,10exposée publiquement uniquement via le tunnel ngrok www.api-ka.com."""1112from __future__ import annotations1314import asyncio15import json16from collections.abc import AsyncIterator17from contextlib import asynccontextmanager, suppress18from pathlib import Path19from typing import Any2021from fastapi import FastAPI, Request22from fastapi.middleware.cors import CORSMiddleware23from fastapi.responses import FileResponse, PlainTextResponse, Response24from fastapi.staticfiles import StaticFiles2526from src.api import reqstats27from src.api.middleware.logging import RequestLoggingMiddleware28from src.api.middleware.kaauth import KaAuthMiddleware29from src.api.middleware.ratelimit import RateLimitMiddleware30from src.api.routes import (31 agent,32 auth,33 envelope,34 health,35 iosauth,36 monitoring,37 runs,38 search,39 services,40 stats,41)42from src.config import SERVICES, get_settings, verify_node43from src.database.db import init_db44from src.utils.logger import get_logger4546VERSION = "1.0.0"474849@asynccontextmanager50async def lifespan(app: FastAPI) -> AsyncIterator[None]:51 """Au démarrage : vérifie le node m3u96b, initialise le schéma DB et lance52 la tâche de fond qui persiste le journal léger des requêtes (/stats)."""53 verify_node()54 init_db()55 get_logger("apika.api").info(56 "API-KA démarrée",57 extra={"version": VERSION, "node": get_settings().node_name},58 )59 flusher = asyncio.create_task(reqstats.flusher_task())60 try:61 yield62 finally:63 flusher.cancel()64 with suppress(asyncio.CancelledError):65 await flusher666768app = FastAPI(69 title="API-KA",70 description=(71 "Plateforme centrale de l'écosystème KA — données quotidiennes des services "72 "lou-ka, immo-ka, food-ka, auto-ka et fabri-ka. Node : m3u96b. "73 "Un service Groupe KA (www.groupe-ka.com)."74 ),75 version=VERSION,76 lifespan=lifespan,77)78# CORS — le widget KA Agent appelle cette API depuis les 13 sites79app.add_middleware(80 CORSMiddleware,81 allow_origins=['https://www.groupe-ka.com', 'https://groupe-ka.com', 'https://www.trouve-ka.com', 'https://trouve-ka.com', 'https://www.lou-ka.com', 'https://lou-ka.com', 'https://www.immo-ka.com', 'https://immo-ka.com', 'https://www.house-ka.com', 'https://house-ka.com', 'https://www.vrai-prix.com', 'https://vrai-prix.com', 'https://www.auto-ka.com', 'https://auto-ka.com', 'https://www.fabri-ka.com', 'https://fabri-ka.com', 'https://www.food-ka.com', 'https://food-ka.com', 'https://www.resto-ka.com', 'https://resto-ka.com', 'https://www.sorti-ka.com', 'https://sorti-ka.com', 'https://www.crea-ka.com', 'https://crea-ka.com', 'https://www.api-ka.com', 'https://api-ka.com', 'https://www.job-ka.com', 'https://job-ka.com'],82 allow_methods=["GET", "POST"],83 allow_headers=["content-type"],84)858687# Authentification KA ID/jeton sur l'API produit (voir middleware/kaauth.py),88# ajoutée AVANT le rate limiting dans la pile (elle s'exécute donc après lui).89app.add_middleware(KaAuthMiddleware)90# Le rate limiting s'applique en premier sur la requête entrante ; la91# journalisation englobe tout (elle logge aussi les réponses 429).92app.add_middleware(RateLimitMiddleware)93app.add_middleware(RequestLoggingMiddleware)9495# /api/v1/runs et /api/v1/search doivent être enregistrés avant les routes96# génériques /api/v1/{service} (sinon leur catch-all capturerait ces chemins).97app.include_router(health.router)98app.include_router(auth.router)99app.include_router(runs.router)100app.include_router(monitoring.router)101app.include_router(stats.router)102app.include_router(search.router)103app.include_router(services.router)104app.include_router(agent.router)105app.include_router(iosauth.router)106107108WEB_DIR = Path(__file__).resolve().parent / "web"109WEB_INDEX = WEB_DIR / "index.html"110WEB_CONTACT = WEB_DIR / "contact.html"111WEB_STATS = WEB_DIR / "stats.html"112KA_DIR = WEB_DIR / "ka"113114# Package ka-ui vendorisé (tokens.css, ecosystem.json, ka-shell.js) — servi115# statiquement pour le site de documentation. Voir frontend → src/api/web/ka.116if KA_DIR.is_dir():117 app.mount("/ka", StaticFiles(directory=KA_DIR), name="ka")118119# Documentation utilisateur (guide pas à pas + PDF) — html=True sert120# web/doc/index.html à /doc/ (et redirige /doc → /doc/).121DOC_DIR = WEB_DIR / "doc"122if DOC_DIR.is_dir():123 app.mount("/doc", StaticFiles(directory=DOC_DIR, html=True), name="doc")124125126@app.get("/", tags=["root"])127@app.head("/", include_in_schema=False)128def root(request: Request) -> Any:129 """Statut de la plateforme + version.130131 Négociation de contenu : les navigateurs (Accept: text/html) reçoivent la132 plateforme web de documentation avec playground ; les clients API reçoivent133 le statut JSON uniforme.134 """135 accept = request.headers.get("accept", "")136 if "text/html" in accept and WEB_INDEX.is_file():137 return FileResponse(WEB_INDEX, media_type="text/html")138 return envelope(139 {140 "platform": "API-KA",141 "status": "ok",142 "version": VERSION,143 "services": list(SERVICES),144 "docs": "/docs",145 }146 )147148149@app.get("/contact", tags=["root"])150def contact(request: Request) -> Any:151 """Page contact Groupe KA — HTML pour les navigateurs, JSON pour les clients API.152153 Les coordonnées proviennent d'ecosystem.json (source de vérité ka-ui).154 """155 accept = request.headers.get("accept", "")156 if "text/html" in accept and WEB_CONTACT.is_file():157 return FileResponse(WEB_CONTACT, media_type="text/html")158 eco_path = KA_DIR / "ecosystem.json"159 eco = json.loads(eco_path.read_text(encoding="utf-8")) if eco_path.is_file() else {}160 return envelope(161 {162 "org": eco.get("org", {}).get("name", "Groupe KA"),163 "hub": eco.get("hub", {}).get("url", "https://www.groupe-ka.com"),164 "contacts": eco.get("contacts", []),165 }166 )167168169@app.get("/stats", tags=["root"], include_in_schema=False)170def stats_page() -> FileResponse:171 """Page /stats — tableau de bord analytique de la plateforme (HTML statique).172173 Les données proviennent de GET /api/stats/dashboard ; l'export PDF de174 GET /api/stats/report (gabarit Groupe-KA).175 """176 return FileResponse(WEB_STATS, media_type="text/html")177178179# --- Référencement du site de documentation (www.api-ka.com) -------------------180181BASE_URL = "https://www.api-ka.com"182183184@app.get("/robots.txt", include_in_schema=False)185def robots_txt() -> PlainTextResponse:186 return PlainTextResponse(187 "User-agent: *\n"188 "Allow: /\n"189 "Disallow: /api/\n"190 "Disallow: /docs\n"191 "Disallow: /openapi.json\n"192 f"\nSitemap: {BASE_URL}/sitemap.xml\n")193194195@app.get("/sitemap.xml", include_in_schema=False)196def sitemap_xml() -> Response:197 urls = "".join(198 f"<url><loc>{BASE_URL}{p}</loc></url>"199 for p in ("/", "/contact", "/stats"))200 return Response(201 '<?xml version="1.0" encoding="UTF-8"?>\n'202 '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'203 + urls + "</urlset>",204 media_type="application/xml",205 headers={"Cache-Control": "public, max-age=3600"})206207208# Assets de marque Groupe KA servis à la racine du domaine (voir src/api/web/) :209# favicon SVG, icône iOS et image Open Graph 1200x630 referencée par les meta og:image.210@app.get("/favicon.svg", include_in_schema=False)211def favicon() -> FileResponse:212 return FileResponse(WEB_DIR / "favicon.svg", media_type="image/svg+xml")213214215@app.get("/apple-touch-icon.png", include_in_schema=False)216def apple_touch_icon() -> FileResponse:217 return FileResponse(WEB_DIR / "apple-touch-icon.png", media_type="image/png")218219220@app.get("/og.png", include_in_schema=False)221def og_image() -> FileResponse:222 return FileResponse(WEB_DIR / "og.png", media_type="image/png")223224225@app.get("/ka-agent.js", include_in_schema=False)226def ka_agent_js() -> FileResponse:227 """Widget KA Agent — bulle de chat IA commune a l ecosysteme Groupe KA."""228 return FileResponse(WEB_DIR / "ka-agent.js", media_type="application/javascript")229