117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
"""Paramètres persistés en DB (activation des sources, santé, réglages UI…)."""
|
|
import json
|
|
import logging
|
|
import secrets
|
|
from datetime import UTC, datetime
|
|
|
|
from app.db import db
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def get_setting(key: str, default: object = None) -> object:
|
|
row = await db.fetchone("SELECT value FROM settings WHERE key = ?", (key,))
|
|
if row is None:
|
|
return default
|
|
try:
|
|
return json.loads(row["value"])
|
|
except json.JSONDecodeError:
|
|
logger.warning("Paramètre %r corrompu, valeur par défaut utilisée", key)
|
|
return default
|
|
|
|
|
|
async def set_setting(key: str, value: object) -> None:
|
|
await db.execute(
|
|
"INSERT INTO settings (key, value) VALUES (?, ?) "
|
|
"ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
|
(key, json.dumps(value, ensure_ascii=False)),
|
|
)
|
|
|
|
|
|
async def is_source_enabled(name: str) -> bool:
|
|
return bool(await get_setting(f"source:{name}:enabled", True))
|
|
|
|
|
|
async def set_source_enabled(name: str, enabled: bool) -> None:
|
|
await set_setting(f"source:{name}:enabled", enabled)
|
|
logger.info("Source %s %s", name, "activée" if enabled else "désactivée")
|
|
|
|
|
|
async def get_source_health(name: str) -> dict | None:
|
|
"""Dernier état de santé connu d'une source (None si jamais testée)."""
|
|
value = await get_setting(f"source:{name}:health")
|
|
return value if isinstance(value, dict) else None
|
|
|
|
|
|
async def set_source_health(name: str, healthy: bool, detail: str) -> dict:
|
|
state = {
|
|
"healthy": healthy,
|
|
"detail": detail,
|
|
"checked_at": datetime.now(UTC).isoformat(),
|
|
}
|
|
await set_setting(f"source:{name}:health", state)
|
|
return state
|
|
|
|
|
|
async def get_source_base_url(name: str) -> str | None:
|
|
"""URL personnalisée d'une source (None = valeur par défaut du code)."""
|
|
value = await get_setting(f"source:{name}:base_url")
|
|
return value if isinstance(value, str) and value else None
|
|
|
|
|
|
async def set_source_base_url(name: str, url: str | None) -> None:
|
|
"""Persiste l'URL personnalisée (None/'' → retour à la valeur par défaut)."""
|
|
key = f"source:{name}:base_url"
|
|
if url:
|
|
await set_setting(key, url)
|
|
else:
|
|
await db.execute("DELETE FROM settings WHERE key = ?", (key,))
|
|
|
|
|
|
async def apply_source_base_urls() -> None:
|
|
"""Applique les URL personnalisées aux instances de sources (au démarrage)."""
|
|
from app.scrapers.base import all_sources
|
|
|
|
for source in all_sources():
|
|
override = await get_source_base_url(source.name)
|
|
if override and override != type(source).base_url:
|
|
source.base_url = override
|
|
logger.info("URL personnalisée pour %s : %s", source.name, override)
|
|
|
|
# ---------------------------------------------------------------- intégrations *arr
|
|
|
|
TORZNAB_APIKEY_KEY = "torznab:apikey"
|
|
SONARR_URL_KEY = "sonarr:url"
|
|
SONARR_APIKEY_KEY = "sonarr:apikey"
|
|
|
|
|
|
async def get_torznab_apikey() -> str:
|
|
"""Clé API Torznab (générée au premier appel, persistée en DB)."""
|
|
key = await get_setting(TORZNAB_APIKEY_KEY)
|
|
if not isinstance(key, str) or not key:
|
|
key = secrets.token_hex(16)
|
|
await set_setting(TORZNAB_APIKEY_KEY, key)
|
|
logger.info("Clé API Torznab générée")
|
|
return key
|
|
|
|
|
|
async def reset_torznab_apikey() -> str:
|
|
key = secrets.token_hex(16)
|
|
await set_setting(TORZNAB_APIKEY_KEY, key)
|
|
logger.info("Clé API Torznab régénérée")
|
|
return key
|
|
|
|
|
|
async def get_sonarr_config() -> dict[str, str]:
|
|
return {
|
|
"url": await get_setting(SONARR_URL_KEY, ""),
|
|
"apikey": await get_setting(SONARR_APIKEY_KEY, ""),
|
|
}
|
|
|
|
|
|
async def set_sonarr_config(url: str, apikey: str) -> None:
|
|
await set_setting(SONARR_URL_KEY, url.rstrip("/"))
|
|
await set_setting(SONARR_APIKEY_KEY, apikey.strip())
|
|
logger.info("Configuration Sonarr enregistrée (%s)", url)
|
|
|