import json from typing import Any, Dict, List from sqlmodel import Session, select from models.platform import PlatformSetting from schemas.platform import SystemSettingPayload from services.platform_settings_core import ( ACTIVITY_EVENT_RETENTION_SETTING_KEY, DEFAULT_ACTIVITY_EVENT_RETENTION_DAYS, DEPRECATED_SETTING_KEYS, PROTECTED_SETTING_KEYS, SETTING_KEYS, SYSTEM_SETTING_DEFINITIONS, _bootstrap_platform_setting_values, _normalize_activity_event_retention_days, _normalize_setting_key, _read_setting_value, _setting_item_from_row, _upsert_setting_row, _utcnow, ) def _coerce_auth_ttl_hours_from_legacy(value: Any) -> int: try: normalized = int(value) except Exception: normalized = 0 return max(1, min(720, normalized * 24)) def ensure_default_system_settings(session: Session) -> None: bootstrap_values = _bootstrap_platform_setting_values() legacy_row = session.get(PlatformSetting, "global") if legacy_row is not None: try: legacy_data = _read_setting_value(legacy_row) except Exception: legacy_data = {} if isinstance(legacy_data, dict): for key in SETTING_KEYS: meta = SYSTEM_SETTING_DEFINITIONS[key] _upsert_setting_row( session, key, name=str(meta["name"]), category=str(meta["category"]), description=str(meta["description"]), value_type=str(meta["value_type"]), value=legacy_data.get(key, bootstrap_values.get(key, meta["value"])), is_public=bool(meta["is_public"]), sort_order=int(meta["sort_order"]), ) session.delete(legacy_row) session.commit() legacy_auth_ttl_hours = None dirty = False for key in DEPRECATED_SETTING_KEYS: legacy_row = session.get(PlatformSetting, key) if legacy_row is not None: if key in {"sys_auth_token_ttl_days", "auth_token_ttl_days"} and legacy_auth_ttl_hours is None: try: legacy_auth_ttl_hours = _coerce_auth_ttl_hours_from_legacy(_read_setting_value(legacy_row)) except Exception: legacy_auth_ttl_hours = None session.delete(legacy_row) dirty = True for key, meta in SYSTEM_SETTING_DEFINITIONS.items(): row = session.get(PlatformSetting, key) default_value = bootstrap_values.get(key, meta["value"]) if key == "auth_token_ttl_hours" and legacy_auth_ttl_hours is not None: default_value = legacy_auth_ttl_hours if row is None: _upsert_setting_row( session, key, name=str(meta["name"]), category=str(meta["category"]), description=str(meta["description"]), value_type=str(meta["value_type"]), value=default_value, is_public=bool(meta["is_public"]), sort_order=int(meta["sort_order"]), ) dirty = True continue changed = False if key == "auth_token_ttl_hours" and legacy_auth_ttl_hours is not None: try: current_value = int(_read_setting_value(row)) except Exception: current_value = int(meta["value"]) if current_value == int(meta["value"]) and legacy_auth_ttl_hours != current_value: row.value_type = str(meta["value_type"]) row.value_json = json.dumps(legacy_auth_ttl_hours, ensure_ascii=False) changed = True for field in ("name", "category", "description", "value_type"): value = str(meta[field]) if key in PROTECTED_SETTING_KEYS: if getattr(row, field) != value: setattr(row, field, value) changed = True elif not getattr(row, field): setattr(row, field, value) changed = True if key in PROTECTED_SETTING_KEYS: if int(getattr(row, "sort_order", 100) or 100) != int(meta["sort_order"]): row.sort_order = int(meta["sort_order"]) changed = True if bool(getattr(row, "is_public", False)) != bool(meta["is_public"]): row.is_public = bool(meta["is_public"]) changed = True elif getattr(row, "sort_order", None) is None: row.sort_order = int(meta["sort_order"]) changed = True if key not in PROTECTED_SETTING_KEYS and getattr(row, "is_public", None) is None: row.is_public = bool(meta["is_public"]) changed = True if changed: row.updated_at = _utcnow() session.add(row) dirty = True if dirty: session.commit() def list_system_settings(session: Session, search: str = "") -> List[Dict[str, Any]]: ensure_default_system_settings(session) stmt = select(PlatformSetting).order_by(PlatformSetting.sort_order.asc(), PlatformSetting.key.asc()) rows = session.exec(stmt).all() keyword = str(search or "").strip().lower() items = [_setting_item_from_row(row) for row in rows] if not keyword: return items return [ item for item in items if keyword in str(item["key"]).lower() or keyword in str(item["name"]).lower() or keyword in str(item["category"]).lower() or keyword in str(item["description"]).lower() ] def create_or_update_system_setting(session: Session, payload: SystemSettingPayload) -> Dict[str, Any]: ensure_default_system_settings(session) normalized_key = _normalize_setting_key(payload.key) definition = SYSTEM_SETTING_DEFINITIONS.get(normalized_key, {}) row = _upsert_setting_row( session, payload.key, name=payload.name or str(definition.get("name") or payload.key), category=payload.category or str(definition.get("category") or "general"), description=payload.description or str(definition.get("description") or ""), value_type=payload.value_type or str(definition.get("value_type") or "json"), value=payload.value if payload.value is not None else definition.get("value"), is_public=payload.is_public, sort_order=payload.sort_order or int(definition.get("sort_order") or 100), ) if normalized_key == ACTIVITY_EVENT_RETENTION_SETTING_KEY: from services.platform_activity_service import prune_expired_activity_events prune_expired_activity_events(session, force=True) session.commit() session.refresh(row) return _setting_item_from_row(row) def delete_system_setting(session: Session, key: str) -> None: normalized_key = _normalize_setting_key(key) if normalized_key in PROTECTED_SETTING_KEYS: raise ValueError("Core platform settings cannot be deleted") row = session.get(PlatformSetting, normalized_key) if row is None: raise ValueError("Setting not found") session.delete(row) session.commit() def get_activity_event_retention_days(session: Session) -> int: row = session.get(PlatformSetting, ACTIVITY_EVENT_RETENTION_SETTING_KEY) if row is None: return DEFAULT_ACTIVITY_EVENT_RETENTION_DAYS try: value = _read_setting_value(row) except Exception: value = DEFAULT_ACTIVITY_EVENT_RETENTION_DAYS return _normalize_activity_event_retention_days(value)