"""User settings validation and defaults.""" import json import logging import re from config import MAX_COMMUNITY_NAME_LENGTH from security import is_safe_filterset logger = logging.getLogger(__name__) class SettingsService: """Parse and sanitize user settings JSON shared by page and API routes.""" EXPERIENCE_DEFAULTS = { "infinite_scroll": False, "auto_refresh": False, "push_notifications": False, "dark_patterns_opt_in": False, "time_filter_enabled": False, "time_filter_days": 7, } EXPERIENCE_BOOL_FIELDS = { "infinite_scroll", "auto_refresh", "push_notifications", "dark_patterns_opt_in", "time_filter_enabled", } @classmethod def parse(cls, settings_str): """Return settings JSON as a dict, or an empty dict if invalid.""" if not settings_str: return {} try: settings = json.loads(settings_str) except json.JSONDecodeError as e: logger.warning(f"Invalid user settings JSON: {e}") return {} if not isinstance(settings, dict): logger.warning("User settings must be a JSON object") return {} return settings @classmethod def validate(cls, settings_str): """Validate and sanitize persisted user settings JSON.""" settings = cls.parse(settings_str) validated = {} filter_set = settings.get("filter_set") if isinstance(filter_set, str) and is_safe_filterset(filter_set): validated["filter_set"] = filter_set communities = settings.get("communities") if isinstance(communities, list): safe_communities = [] for community in communities: if ( isinstance(community, str) and len(community) <= MAX_COMMUNITY_NAME_LENGTH and re.match(r"^[a-zA-Z0-9_-]+$", community) ): safe_communities.append(community) validated["communities"] = safe_communities experience = settings.get("experience") if isinstance(experience, dict): safe_experience = {} for field in cls.EXPERIENCE_BOOL_FIELDS: if field in experience and isinstance(experience[field], bool): safe_experience[field] = experience[field] time_filter_days = experience.get("time_filter_days") if isinstance(time_filter_days, int) and time_filter_days > 0: safe_experience["time_filter_days"] = time_filter_days validated["experience"] = safe_experience return validated @classmethod def experience_settings(cls, settings_str): """Return experience settings with defaults filled in.""" settings = cls.parse(settings_str) experience = settings.get("experience", {}) if not isinstance(experience, dict): experience = {} return {**cls.EXPERIENCE_DEFAULTS, **experience}