import json from pathlib import Path import structlog from app.config import settings logger = structlog.get_logger(__name__) class SupportSettingsService: """Runtime editable support settings with JSON persistence.""" _storage_path: Path = Path('data/support_settings.json') _data: dict = {} _loaded: bool = False @classmethod def _ensure_dir(cls) -> None: try: cls._storage_path.parent.mkdir(parents=True, exist_ok=True) except Exception as e: logger.error('Failed to ensure settings dir', error=e) @classmethod def _load(cls) -> None: if cls._loaded: return cls._ensure_dir() try: if cls._storage_path.exists(): cls._data = json.loads(cls._storage_path.read_text(encoding='utf-8')) else: cls._data = {} except Exception as e: logger.error('Failed to load support settings', error=e) cls._data = {} cls._loaded = True @classmethod def _save(cls) -> bool: cls._ensure_dir() try: cls._storage_path.write_text(json.dumps(cls._data, ensure_ascii=False, indent=2), encoding='utf-8') return True except Exception as e: logger.error('Failed to save support settings', error=e) return False # Mode @classmethod def get_system_mode(cls) -> str: cls._load() mode = (cls._data.get('system_mode') or settings.get_support_system_mode()).strip().lower() return mode if mode in {'tickets', 'contact', 'both'} else 'both' @classmethod def set_system_mode(cls, mode: str) -> bool: mode_clean = (mode or '').strip().lower() if mode_clean not in {'tickets', 'contact', 'both'}: return False cls._load() cls._data['system_mode'] = mode_clean settings.SUPPORT_SYSTEM_MODE = mode_clean return cls._save() # Main menu visibility @classmethod def is_support_menu_enabled(cls) -> bool: cls._load() if 'menu_enabled' in cls._data: return bool(cls._data['menu_enabled']) return bool(settings.SUPPORT_MENU_ENABLED) @classmethod def set_support_menu_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['menu_enabled'] = bool(enabled) return cls._save() # Contact vs tickets helpers @classmethod def is_tickets_enabled(cls) -> bool: return cls.get_system_mode() in {'tickets', 'both'} @classmethod def is_contact_enabled(cls) -> bool: return cls.get_system_mode() in {'contact', 'both'} # Descriptions (per language) @classmethod def get_support_info_text(cls, language: str) -> str: cls._load() lang = (language or settings.DEFAULT_LANGUAGE).split('-')[0].lower() overrides = cls._data.get('support_info_texts') or {} text = overrides.get(lang) if text and isinstance(text, str) and text.strip(): return text # Fallback to dynamic localization default from app.localization.texts import get_texts return get_texts(lang).SUPPORT_INFO @classmethod def set_support_info_text(cls, language: str, text: str) -> bool: cls._load() lang = (language or settings.DEFAULT_LANGUAGE).split('-')[0].lower() texts_map = cls._data.get('support_info_texts') or {} texts_map[lang] = text or '' cls._data['support_info_texts'] = texts_map return cls._save() # Notifications & SLA @classmethod def get_admin_ticket_notifications_enabled(cls) -> bool: cls._load() if 'admin_ticket_notifications_enabled' in cls._data: return bool(cls._data['admin_ticket_notifications_enabled']) # fallback to global admin notifications setting return bool(settings.is_admin_notifications_enabled()) @classmethod def set_admin_ticket_notifications_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['admin_ticket_notifications_enabled'] = bool(enabled) return cls._save() @classmethod def get_user_ticket_notifications_enabled(cls) -> bool: cls._load() if 'user_ticket_notifications_enabled' in cls._data: return bool(cls._data['user_ticket_notifications_enabled']) # fallback to global enable notifications return bool(getattr(settings, 'ENABLE_NOTIFICATIONS', True)) @classmethod def set_user_ticket_notifications_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['user_ticket_notifications_enabled'] = bool(enabled) return cls._save() @classmethod def get_sla_enabled(cls) -> bool: cls._load() if 'ticket_sla_enabled' in cls._data: return bool(cls._data['ticket_sla_enabled']) return bool(getattr(settings, 'SUPPORT_TICKET_SLA_ENABLED', True)) @classmethod def set_sla_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['ticket_sla_enabled'] = bool(enabled) return cls._save() @classmethod def get_sla_minutes(cls) -> int: cls._load() minutes = cls._data.get('ticket_sla_minutes') if isinstance(minutes, int) and minutes > 0: return minutes return int(getattr(settings, 'SUPPORT_TICKET_SLA_MINUTES', 5)) @classmethod def set_sla_minutes(cls, minutes: int) -> bool: try: minutes_int = int(minutes) except Exception: return False if minutes_int <= 0: return False cls._load() cls._data['ticket_sla_minutes'] = minutes_int return cls._save() # Moderators management @classmethod def get_moderators(cls) -> list[int]: cls._load() raw = cls._data.get('moderators') or [] moderators: list[int] = [] for item in raw: try: moderators.append(int(item)) except Exception: continue return moderators @classmethod def is_moderator(cls, telegram_id: int) -> bool: try: tid = int(telegram_id) except Exception: return False return tid in cls.get_moderators() @classmethod def add_moderator(cls, telegram_id: int) -> bool: try: tid = int(telegram_id) except Exception: return False cls._load() moderators = set(cls.get_moderators()) moderators.add(tid) cls._data['moderators'] = sorted(moderators) return cls._save() @classmethod def remove_moderator(cls, telegram_id: int) -> bool: try: tid = int(telegram_id) except Exception: return False cls._load() moderators = set(cls.get_moderators()) if tid in moderators: moderators.remove(tid) cls._data['moderators'] = sorted(moderators) return cls._save() return True # Cabinet notifications (веб-кабинет) @classmethod def get_cabinet_user_notifications_enabled(cls) -> bool: """Уведомления юзерам в кабинет о ответе админа на тикет.""" cls._load() if 'cabinet_user_notifications_enabled' in cls._data: return bool(cls._data['cabinet_user_notifications_enabled']) return True # По умолчанию включено @classmethod def set_cabinet_user_notifications_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['cabinet_user_notifications_enabled'] = bool(enabled) return cls._save() @classmethod def get_cabinet_admin_notifications_enabled(cls) -> bool: """Уведомления админам в кабинет о новых тикетах.""" cls._load() if 'cabinet_admin_notifications_enabled' in cls._data: return bool(cls._data['cabinet_admin_notifications_enabled']) return True # По умолчанию включено @classmethod def set_cabinet_admin_notifications_enabled(cls, enabled: bool) -> bool: cls._load() cls._data['cabinet_admin_notifications_enabled'] = bool(enabled) return cls._save()