SPB Git forge

spb/api-ka

Public

API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com

48commits 1branches 0releases
5.9 MBsize
maindefault branch
19 days agolast push
Python 60.9% HTML 21% TypeScript 7.3% JavaScript 5.2% CSS 4.8% Shell 0.8%
8.2 KB · 229 lines python
Raw Blame History
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