feat(forge): fondation multi-forges — connexions chiffrées, registre, client GitHub (P0/P1)
- Tables forge_connections/users/workspaces (+ membres, projets, états, audit) ; migration des anciennes bases avec portées (workspace_id, connection_id) par défaut (1, 1). - Jetons chiffrés Fernet (KANBANTE_MASTER_KEY ou clé générée en base), jamais réaffichés. - ForgeRegistry : connexion héritée créée depuis l'environnement, espace Principal, instantanés préfixés par connexion. - GitHubClient complet (Bearer, pagination Link, étiquettes par nom, PATCH archived, branches via git/refs). - Paramètres surchargeables en base (résolveur config, P3).
This commit is contained in:
+71
-2
@@ -104,7 +104,7 @@ from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
APP_VERSION = "0.23.3"
|
||||
APP_VERSION = "0.24.0"
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool) -> bool:
|
||||
@@ -179,6 +179,7 @@ class Settings:
|
||||
draco_secret_prev: str = ""
|
||||
sso_machine_key: str = ""
|
||||
sso_url: str = "https://og.dracodev.net"
|
||||
archive_suggest_days: int = 180
|
||||
|
||||
@property
|
||||
def auth_enabled(self) -> bool:
|
||||
@@ -186,7 +187,13 @@ class Settings:
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
"""Build settings from the current environment."""
|
||||
"""Effective settings: environment, overlaid by the admin
|
||||
panel's database values (0.24.0 — see OVERRIDABLE_SETTINGS)."""
|
||||
return apply_db_overrides(_env_settings())
|
||||
|
||||
|
||||
def _env_settings() -> Settings:
|
||||
"""Build settings from the current environment only."""
|
||||
return Settings(
|
||||
gitea_url=os.getenv("GITEA_URL", "https://git.dracodev.net").rstrip("/"),
|
||||
gitea_token=os.getenv("GITEA_TOKEN", ""),
|
||||
@@ -230,4 +237,66 @@ def get_settings() -> Settings:
|
||||
draco_secret_prev=os.getenv("DRACO_SESSION_SECRET_PREV", ""),
|
||||
sso_machine_key=os.getenv("DRACO_SSO_MACHINE_KEY", ""),
|
||||
sso_url=os.getenv("DRACO_SSO_URL", "https://og.dracodev.net").rstrip("/"),
|
||||
archive_suggest_days=_env_int("KANBANTE_ARCHIVE_SUGGEST_DAYS", 180),
|
||||
)
|
||||
|
||||
|
||||
# ── Surcharge par le panneau d'administration (0.24.0, P3) ──
|
||||
# L'environnement amorce, le panneau gouverne : les valeurs
|
||||
# ci-dessous peuvent être remplacées en base (kv_store, clé
|
||||
# "settings_overrides", JSON) depuis /admin/settings. La
|
||||
# provenance reste affichée dans le panneau. Les secrets et les
|
||||
# réglages d'amorçage (DB, clé maître, session, draco) ne sont
|
||||
# volontairement PAS surchargeables.
|
||||
OVERRIDABLE_SETTINGS: dict[str, type] = {
|
||||
"active_window_days": int,
|
||||
"cache_ttl": float,
|
||||
"snapshot_interval": int,
|
||||
"aging_days": int,
|
||||
"zombie_days": int,
|
||||
"archive_suggest_days": int,
|
||||
"max_note_chars": int,
|
||||
"briefing_time": str,
|
||||
"briefing_include_standup": bool,
|
||||
"digest_time": str,
|
||||
"digest_weekday": int,
|
||||
"capture_repo": str,
|
||||
"sync_labels": bool,
|
||||
"sync_gitea": bool,
|
||||
}
|
||||
|
||||
|
||||
def settings_overrides() -> dict:
|
||||
"""The raw overrides stored by the admin panel ({} if none)."""
|
||||
try:
|
||||
import json as _json
|
||||
|
||||
from app import models
|
||||
|
||||
raw = models.get_kv("settings_overrides")
|
||||
data = _json.loads(raw) if raw else {}
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def apply_db_overrides(settings: Settings) -> Settings:
|
||||
"""Overlay the admin-panel values on the env-built settings."""
|
||||
overrides = settings_overrides()
|
||||
if not overrides:
|
||||
return settings
|
||||
import dataclasses
|
||||
|
||||
valid: dict = {}
|
||||
for key, value in overrides.items():
|
||||
converter = OVERRIDABLE_SETTINGS.get(key)
|
||||
if converter is None or value is None:
|
||||
continue
|
||||
try:
|
||||
if converter is bool:
|
||||
valid[key] = str(value).lower() in ("1", "true", "on", "oui")
|
||||
else:
|
||||
valid[key] = converter(value)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return dataclasses.replace(settings, **valid) if valid else settings
|
||||
|
||||
Reference in New Issue
Block a user