from __future__ import annotations import json from datetime import datetime, timezone from typing import Any from paths import DATA_DIR SETTINGS_FILE = DATA_DIR / "settings.json" SETTINGS_FILE.parent.mkdir(parents=True, exist_ok=True) SECRET_FIELDS = { "gitlab_token", "xai_api_key", "pts_password", "teams_password", "compbase_password", } PASSWORD_FIELDS = {"pts_password", "teams_password", "compbase_password"} MASK_PLACEHOLDER = "********" def _now_iso() -> str: return datetime.now(timezone.utc).isoformat() def load_overrides() -> dict[str, Any]: if not SETTINGS_FILE.exists(): return {} try: payload = json.loads(SETTINGS_FILE.read_text(encoding="utf-8")) return payload.get("values", payload) if isinstance(payload, dict) else {} except (json.JSONDecodeError, OSError): return {} def save_overrides(values: dict[str, Any]) -> None: SETTINGS_FILE.write_text( json.dumps( {"updated_at": _now_iso(), "values": values}, ensure_ascii=False, indent=2, ), encoding="utf-8", ) def mask_secret(value: str) -> str: if not value: return "" if len(value) <= 10: return MASK_PLACEHOLDER return f"{value[:6]}...{value[-4:]}" def is_masked_or_empty(value: Any) -> bool: if value is None: return True text = str(value).strip() if not text: return True if text == MASK_PLACEHOLDER: return True if "..." in text and len(text) < 30: return True return False def merge_secret_updates( current: dict[str, Any], updates: dict[str, Any], existing_secrets: dict[str, str], ) -> dict[str, Any]: merged = {**current, **updates} for field in SECRET_FIELDS: if field in updates and is_masked_or_empty(updates[field]): if existing_secrets.get(field): merged[field] = existing_secrets[field] else: merged.pop(field, None) return merged def public_view(settings: dict[str, Any]) -> dict[str, Any]: public = dict(settings) for field in SECRET_FIELDS: raw = public.get(field, "") public[f"{field}_set"] = bool(raw) public[field] = mask_secret(raw) if raw else "" public["gitlab_board_label_list"] = [ part.strip() for part in str(public.get("gitlab_board_labels", "")).split(",") if part.strip() ] public["auto_fill_time"] = ( f"{int(public.get('auto_fill_hour', 18)):02d}:" f"{int(public.get('auto_fill_minute', 0)):02d}" ) return public