from __future__ import annotations import json import os from pathlib import Path from typing import Any from app.core.config import settings SETTINGS_ENV_KEYS: dict[str, str] = { "enable_rate_limit": "ENABLE_RATE_LIMIT", "enable_docs": "ENABLE_DOCS", "cookie_secure": "COOKIE_SECURE", "jwt_access_ttl_min": "JWT_ACCESS_TTL_MIN", "auth_lockout_attempts": "AUTH_LOCKOUT_ATTEMPTS", "auth_lockout_minutes": "AUTH_LOCKOUT_MINUTES", "cors_origins": "CORS_ORIGINS", "frontend_url": "FRONTEND_URL", "public_base_url": "PUBLIC_BASE_URL", "smtp_host": "SMTP_HOST", "smtp_port": "SMTP_PORT", "smtp_from": "SMTP_FROM", "avatar_max_bytes": "AVATAR_MAX_BYTES", "media_url_ttl_seconds": "MEDIA_URL_TTL_SECONDS", "log_level": "LOG_LEVEL", "audit_retention_days": "AUDIT_RETENTION_DAYS", "jwt_refresh_ttl_days": "JWT_REFRESH_TTL_DAYS", } MANAGED_KEYS = tuple(SETTINGS_ENV_KEYS.keys()) def _settings_file() -> Path: return Path(settings.compton_settings_path) def get_settings_values() -> dict[str, Any]: return {key: getattr(settings, key) for key in MANAGED_KEYS} def _coerce_value(key: str, value: Any) -> Any: current = getattr(settings, key) if isinstance(current, bool): if isinstance(value, bool): return value if isinstance(value, str): return value.lower() in {"1", "true", "yes", "on"} return bool(value) if isinstance(current, int): return int(value) if isinstance(current, list): if isinstance(value, list): return [str(item) for item in value] if isinstance(value, str): return [item.strip() for item in value.split(",") if item.strip()] raise ValueError(f"INVALID_LIST_{key}") return value def env_locks() -> dict[str, bool]: return {key: os.getenv(env_key) is not None for key, env_key in SETTINGS_ENV_KEYS.items()} def apply_settings_to_app(values: dict[str, Any]) -> None: for key, value in values.items(): if key not in MANAGED_KEYS: continue setattr(settings, key, _coerce_value(key, value)) def read_settings() -> dict[str, Any]: path = _settings_file() if not path.exists(): return {} with path.open("r", encoding="utf-8") as file: payload = json.load(file) if not isinstance(payload, dict): return {} return {key: payload[key] for key in MANAGED_KEYS if key in payload} def write_settings(partial: dict[str, Any]) -> dict[str, Any]: locks = env_locks() current = get_settings_values() for key, value in partial.items(): if key not in MANAGED_KEYS: continue if locks[key]: continue current[key] = _coerce_value(key, value) path = _settings_file() path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", encoding="utf-8") as file: json.dump(current, file, ensure_ascii=False, indent=2) return current def bootstrap_settings() -> None: apply_settings_to_app(read_settings()) def get_settings_payload() -> dict[str, Any]: locks = env_locks() values = get_settings_values() secrets = { "jwt_access_secret_configured": bool(settings.jwt_access_secret), "jwt_refresh_pepper_configured": bool(settings.jwt_refresh_pepper), "smtp_password_configured": bool(settings.smtp_password), "s3_secret_key_configured": bool(settings.s3_secret_key), } return { "values": values, "locks": locks, "settings_path": str(_settings_file()), "secrets": secrets, }