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/config.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-168# ============================================9"""Configuration centrale d'API-KA : chargement du .env et vérification du node m3u96b."""1011from __future__ import annotations1213import os14import socket15from dataclasses import dataclass16from functools import lru_cache17from pathlib import Path1819from dotenv import load_dotenv2021from src.ka_registry import resolve_source_url2223# Nœud imposé (optionnel) : APIKA_REQUIRED_NODE=m3u96b ; vide = tourne sur n'importe quel nœud (placement par maclustr-dispatch).24REQUIRED_NODE = os.getenv("APIKA_REQUIRED_NODE", "").strip().lower()2526SERVICES: tuple[str, ...] = (27 "louka",28 "immoka",29 "foodka",30 "autoka",31 "fabrika",32 "restoka",33 "sortika",34 "creaka",35 "jobka",36 "houseka",37 "rentka",38)3940SERVICE_TABLES: dict[str, str] = {service: f"{service}_data" for service in SERVICES}4142SERVICE_SOURCE_ENV: dict[str, str] = {43 "louka": "LOUKA_SOURCE_URL",44 "immoka": "IMMOKA_SOURCE_URL",45 "foodka": "FOODKA_SOURCE_URL",46 "autoka": "AUTOKA_SOURCE_URL",47 "fabrika": "FABRIKA_SOURCE_URL",48 "restoka": "RESTOKA_SOURCE_URL",49 "sortika": "SORTIKA_SOURCE_URL",50 "creaka": "CREAKA_SOURCE_URL",51 "jobka": "JOBKA_SOURCE_URL",52 "houseka": "HOUSEKA_SOURCE_URL",53 "rentka": "RENTKA_SOURCE_URL",54}5556BASE_DIR = Path(__file__).resolve().parents[1]5758load_dotenv(BASE_DIR / ".env")596061def verify_node() -> None:62 """Refuse de démarrer en production si le hostname n'est pas m3u96b.6364 Le suffixe éventuel (ex. ``.local``) est ignoré et la comparaison est65 insensible à la casse, mais la règle reste stricte : en production,66 API-KA ne tourne que sur le node m3u96b.67 """68 hostname = socket.gethostname()69 if os.getenv("APP_ENV") == "production" and REQUIRED_NODE:70 short = hostname.split(".")[0].lower()71 if short != REQUIRED_NODE:72 raise RuntimeError(73 f"API-KA doit tourner sur {REQUIRED_NODE}, node actuel : {hostname}"74 )757677@dataclass(frozen=True)78class Settings:79 """Configuration immuable de la plateforme, chargée depuis le .env."""8081 app_env: str82 node_name: str83 database_url: str84 ngrok_authtoken: str85 ngrok_domain: str86 api_port: int87 daily_run_hour: int88 backup_retention_days: int89 rate_limit_per_minute: int90 trouveka_search_url: str91 trouveka_suggest_url: str92 source_urls: dict[str, str]93 base_dir: Path94 logs_dir: Path95 backups_dir: Path969798@lru_cache(maxsize=1)99def get_settings() -> Settings:100 """Charge et met en cache la configuration depuis les variables d'environnement."""101 logs_dir = BASE_DIR / "logs"102 backups_dir = BASE_DIR / "data" / "backups"103 logs_dir.mkdir(parents=True, exist_ok=True)104 backups_dir.mkdir(parents=True, exist_ok=True)105106 default_db = f"sqlite:///{BASE_DIR / 'data' / 'apika_dev.db'}"107 return Settings(108 app_env=os.getenv("APP_ENV", "development"),109 node_name=os.getenv("NODE_NAME", REQUIRED_NODE),110 database_url=os.getenv("DATABASE_URL", default_db),111 ngrok_authtoken=os.getenv("NGROK_AUTHTOKEN", ""),112 ngrok_domain=os.getenv("NGROK_DOMAIN", "www.api-ka.com"),113 api_port=int(os.getenv("API_PORT", "8000")),114 daily_run_hour=int(os.getenv("DAILY_RUN_HOUR", "02")),115 backup_retention_days=max(90, int(os.getenv("BACKUP_RETENTION_DAYS", "90"))),116 rate_limit_per_minute=int(os.getenv("RATE_LIMIT_PER_MINUTE", "120")),117 trouveka_search_url=os.getenv(118 "TROUVEKA_SEARCH_URL", "https://www.trouve-ka.com/api/search"119 ),120 trouveka_suggest_url=os.getenv(121 "TROUVEKA_SUGGEST_URL", "https://www.trouve-ka.com/api/suggest"122 ),123 source_urls={124 service: resolve_source_url(service, os.getenv(env_var, ""))125 for service, env_var in SERVICE_SOURCE_ENV.items()126 },127 base_dir=BASE_DIR,128 logs_dir=logs_dir,129 backups_dir=backups_dir,130 )131132133def source_url(service: str) -> str:134 """URL de collecte LIVE d'un service : registre maclustr-dispatch (suit les135 ``mld move``), repli sur ``<SERVICE>_SOURCE_URL`` du .env."""136 return resolve_source_url(service, os.getenv(SERVICE_SOURCE_ENV.get(service, ""), ""))137