diff --git a/app/bot.py b/app/bot.py index d2b7f56b..a13e2237 100644 --- a/app/bot.py +++ b/app/bot.py @@ -39,7 +39,6 @@ from app.handlers.admin import ( welcome_text as admin_welcome_text, tickets as admin_tickets, reports as admin_reports, - bot_configuration as admin_bot_configuration, ) from app.handlers.stars_payments import register_stars_handlers @@ -142,7 +141,6 @@ async def setup_bot() -> tuple[Bot, Dispatcher]: admin_welcome_text.register_welcome_text_handlers(dp) admin_tickets.register_handlers(dp) admin_reports.register_handlers(dp) - admin_bot_configuration.register_handlers(dp) common.register_handlers(dp) register_stars_handlers(dp) logger.info("⭐ Зарегистрированы обработчики Telegram Stars платежей") diff --git a/app/database/crud/system_setting.py b/app/database/crud/system_setting.py deleted file mode 100644 index 63aaf719..00000000 --- a/app/database/crud/system_setting.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.database.models import SystemSetting - - -async def upsert_system_setting( - db: AsyncSession, - key: str, - value: Optional[str], - description: Optional[str] = None, -) -> SystemSetting: - result = await db.execute( - select(SystemSetting).where(SystemSetting.key == key) - ) - setting = result.scalar_one_or_none() - - if setting is None: - setting = SystemSetting(key=key, value=value, description=description) - db.add(setting) - else: - setting.value = value - if description is not None: - setting.description = description - - await db.flush() - return setting - - -async def delete_system_setting(db: AsyncSession, key: str) -> None: - result = await db.execute( - select(SystemSetting).where(SystemSetting.key == key) - ) - setting = result.scalar_one_or_none() - if setting is not None: - await db.delete(setting) - await db.flush() - diff --git a/app/database/universal_migration.py b/app/database/universal_migration.py index ee1e5cee..b0edb1b6 100644 --- a/app/database/universal_migration.py +++ b/app/database/universal_migration.py @@ -1833,59 +1833,6 @@ async def ensure_server_promo_groups_setup() -> bool: ) return False -async def create_system_settings_table() -> bool: - table_exists = await check_table_exists("system_settings") - if table_exists: - logger.info("ℹ️ Таблица system_settings уже существует") - return True - - try: - async with engine.begin() as conn: - db_type = await get_database_type() - - if db_type == "sqlite": - create_sql = """ - CREATE TABLE system_settings ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key VARCHAR(255) NOT NULL UNIQUE, - value TEXT NULL, - description TEXT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - """ - elif db_type == "postgresql": - create_sql = """ - CREATE TABLE system_settings ( - id SERIAL PRIMARY KEY, - key VARCHAR(255) NOT NULL UNIQUE, - value TEXT NULL, - description TEXT NULL, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() - ); - """ - else: - create_sql = """ - CREATE TABLE system_settings ( - id INT AUTO_INCREMENT PRIMARY KEY, - key VARCHAR(255) NOT NULL UNIQUE, - value TEXT NULL, - description TEXT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - """ - - await conn.execute(text(create_sql)) - logger.info("✅ Таблица system_settings создана") - return True - - except Exception as error: - logger.error(f"Ошибка создания таблицы system_settings: {error}") - return False - - async def run_universal_migration(): logger.info("=== НАЧАЛО УНИВЕРСАЛЬНОЙ МИГРАЦИИ ===") @@ -1897,13 +1844,6 @@ async def run_universal_migration(): if not referral_migration_success: logger.warning("⚠️ Проблемы с миграцией реферальной системы") - logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ SYSTEM_SETTINGS ===") - system_settings_ready = await create_system_settings_table() - if system_settings_ready: - logger.info("✅ Таблица system_settings готова") - else: - logger.warning("⚠️ Проблемы с таблицей system_settings") - logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ CRYPTOBOT ===") cryptobot_created = await create_cryptobot_payments_table() if cryptobot_created: diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py deleted file mode 100644 index 76120f61..00000000 --- a/app/handlers/admin/bot_configuration.py +++ /dev/null @@ -1,410 +0,0 @@ -import math -from typing import Tuple - -from aiogram import Dispatcher, F, types -from aiogram.fsm.context import FSMContext -from sqlalchemy.ext.asyncio import AsyncSession - -from app.database.models import User -from app.localization.texts import get_texts -from app.services.system_settings_service import bot_configuration_service -from app.states import BotConfigStates -from app.utils.decorators import admin_required, error_handler - - -CATEGORY_PAGE_SIZE = 10 -SETTINGS_PAGE_SIZE = 8 - - -def _parse_category_payload(payload: str) -> Tuple[str, int]: - parts = payload.split(":") - if len(parts) == 3: - _, category_key, page_raw = parts - try: - return category_key, max(1, int(page_raw)) - except ValueError: - return category_key, 1 - if len(parts) == 2: - _, category_key = parts - return category_key, 1 - return "", 1 - - -def _build_categories_keyboard(language: str, page: int = 1) -> types.InlineKeyboardMarkup: - categories = bot_configuration_service.get_categories() - total_pages = max(1, math.ceil(len(categories) / CATEGORY_PAGE_SIZE)) - page = max(1, min(page, total_pages)) - - start = (page - 1) * CATEGORY_PAGE_SIZE - end = start + CATEGORY_PAGE_SIZE - sliced = categories[start:end] - - rows: list[list[types.InlineKeyboardButton]] = [] - for category_key, label, count in sliced: - button_text = f"{label} ({count})" - rows.append([ - types.InlineKeyboardButton( - text=button_text, - callback_data=f"botcfg_cat:{category_key}:1", - ) - ]) - - if total_pages > 1: - nav_row: list[types.InlineKeyboardButton] = [] - if page > 1: - nav_row.append( - types.InlineKeyboardButton( - text="⬅️", callback_data=f"botcfg_categories:{page - 1}" - ) - ) - nav_row.append( - types.InlineKeyboardButton( - text=f"{page}/{total_pages}", callback_data="botcfg_categories:noop" - ) - ) - if page < total_pages: - nav_row.append( - types.InlineKeyboardButton( - text="➡️", callback_data=f"botcfg_categories:{page + 1}" - ) - ) - rows.append(nav_row) - - rows.append([ - types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_submenu_settings") - ]) - - return types.InlineKeyboardMarkup(inline_keyboard=rows) - - -def _build_settings_keyboard( - category_key: str, - language: str, - page: int = 1, -) -> types.InlineKeyboardMarkup: - definitions = bot_configuration_service.get_settings_for_category(category_key) - total_pages = max(1, math.ceil(len(definitions) / SETTINGS_PAGE_SIZE)) - page = max(1, min(page, total_pages)) - - start = (page - 1) * SETTINGS_PAGE_SIZE - end = start + SETTINGS_PAGE_SIZE - sliced = definitions[start:end] - - rows: list[list[types.InlineKeyboardButton]] = [] - - for definition in sliced: - value_preview = bot_configuration_service.format_value_for_list(definition.key) - button_text = f"{definition.key} = {value_preview}" - rows.append([ - types.InlineKeyboardButton( - text=button_text, - callback_data=f"botcfg_setting:{definition.key}", - ) - ]) - - if total_pages > 1: - nav_row: list[types.InlineKeyboardButton] = [] - if page > 1: - nav_row.append( - types.InlineKeyboardButton( - text="⬅️", - callback_data=f"botcfg_cat:{category_key}:{page - 1}", - ) - ) - nav_row.append( - types.InlineKeyboardButton( - text=f"{page}/{total_pages}", callback_data="botcfg_cat_page:noop" - ) - ) - if page < total_pages: - nav_row.append( - types.InlineKeyboardButton( - text="➡️", - callback_data=f"botcfg_cat:{category_key}:{page + 1}", - ) - ) - rows.append(nav_row) - - rows.append([ - types.InlineKeyboardButton( - text="⬅️ К категориям", - callback_data="admin_bot_config", - ) - ]) - - return types.InlineKeyboardMarkup(inline_keyboard=rows) - - -def _build_setting_keyboard(key: str) -> types.InlineKeyboardMarkup: - definition = bot_configuration_service.get_definition(key) - rows: list[list[types.InlineKeyboardButton]] = [] - - if definition.python_type is bool: - rows.append([ - types.InlineKeyboardButton( - text="🔁 Переключить", - callback_data=f"botcfg_toggle:{key}", - ) - ]) - - rows.append([ - types.InlineKeyboardButton( - text="✏️ Изменить", - callback_data=f"botcfg_edit:{key}", - ) - ]) - - if bot_configuration_service.has_override(key): - rows.append([ - types.InlineKeyboardButton( - text="♻️ Сбросить", - callback_data=f"botcfg_reset:{key}", - ) - ]) - - rows.append([ - types.InlineKeyboardButton( - text="⬅️ Назад", - callback_data=f"botcfg_cat:{definition.category_key}:1", - ) - ]) - - return types.InlineKeyboardMarkup(inline_keyboard=rows) - - -def _render_setting_text(key: str) -> str: - summary = bot_configuration_service.get_setting_summary(key) - - lines = [ - "🧩 Настройка", - f"Ключ: {summary['key']}", - f"Тип: {summary['type']}", - f"Текущее значение: {summary['current']}", - f"Значение по умолчанию: {summary['original']}", - f"Переопределено в БД: {'✅ Да' if summary['has_override'] else '❌ Нет'}", - ] - - return "\n".join(lines) - - -@admin_required -@error_handler -async def show_bot_config_menu( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - keyboard = _build_categories_keyboard(db_user.language) - await callback.message.edit_text( - "🧩 Конфигурация бота\n\nВыберите категорию настроек:", - reply_markup=keyboard, - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_bot_config_categories_page( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - parts = callback.data.split(":") - try: - page = int(parts[1]) - except (IndexError, ValueError): - page = 1 - - keyboard = _build_categories_keyboard(db_user.language, page) - await callback.message.edit_text( - "🧩 Конфигурация бота\n\nВыберите категорию настроек:", - reply_markup=keyboard, - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_bot_config_category( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - category_key, page = _parse_category_payload(callback.data) - definitions = bot_configuration_service.get_settings_for_category(category_key) - - if not definitions: - await callback.answer("В этой категории пока нет настроек", show_alert=True) - return - - category_label = definitions[0].category_label - keyboard = _build_settings_keyboard(category_key, db_user.language, page) - await callback.message.edit_text( - f"🧩 {category_label}\n\nВыберите настройку для просмотра:", - reply_markup=keyboard, - ) - await callback.answer() - - -@admin_required -@error_handler -async def show_bot_config_setting( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - key = callback.data.split(":", 1)[1] - text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key) - await callback.message.edit_text(text, reply_markup=keyboard) - await callback.answer() - - -@admin_required -@error_handler -async def start_edit_setting( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, - state: FSMContext, -): - key = callback.data.split(":", 1)[1] - definition = bot_configuration_service.get_definition(key) - - summary = bot_configuration_service.get_setting_summary(key) - texts = get_texts(db_user.language) - - instructions = [ - "✏️ Редактирование настройки", - f"Ключ: {summary['key']}", - f"Тип: {summary['type']}", - f"Текущее значение: {summary['current']}", - "\nОтправьте новое значение сообщением.", - ] - - if definition.is_optional: - instructions.append("Отправьте 'none' или оставьте пустым для сброса на значение по умолчанию.") - - instructions.append("Для отмены отправьте 'cancel'.") - - await callback.message.edit_text( - "\n".join(instructions), - reply_markup=types.InlineKeyboardMarkup( - inline_keyboard=[ - [ - types.InlineKeyboardButton( - text=texts.BACK, callback_data=f"botcfg_setting:{key}" - ) - ] - ] - ), - ) - - await state.update_data(setting_key=key) - await state.set_state(BotConfigStates.waiting_for_value) - await callback.answer() - - -@admin_required -@error_handler -async def handle_edit_setting( - message: types.Message, - db_user: User, - db: AsyncSession, - state: FSMContext, -): - data = await state.get_data() - key = data.get("setting_key") - - if not key: - await message.answer("Не удалось определить редактируемую настройку. Попробуйте снова.") - await state.clear() - return - - try: - value = bot_configuration_service.parse_user_value(key, message.text or "") - except ValueError as error: - await message.answer(f"⚠️ {error}") - return - - await bot_configuration_service.set_value(db, key, value) - await db.commit() - - text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key) - await message.answer("✅ Настройка обновлена") - await message.answer(text, reply_markup=keyboard) - await state.clear() - - -@admin_required -@error_handler -async def reset_setting( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - key = callback.data.split(":", 1)[1] - await bot_configuration_service.reset_value(db, key) - await db.commit() - - text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key) - await callback.message.edit_text(text, reply_markup=keyboard) - await callback.answer("Сброшено к значению по умолчанию") - - -@admin_required -@error_handler -async def toggle_setting( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - key = callback.data.split(":", 1)[1] - current = bot_configuration_service.get_current_value(key) - new_value = not bool(current) - await bot_configuration_service.set_value(db, key, new_value) - await db.commit() - - text = _render_setting_text(key) - keyboard = _build_setting_keyboard(key) - await callback.message.edit_text(text, reply_markup=keyboard) - await callback.answer("Обновлено") - - -def register_handlers(dp: Dispatcher) -> None: - dp.callback_query.register( - show_bot_config_menu, - F.data == "admin_bot_config", - ) - dp.callback_query.register( - show_bot_config_categories_page, - F.data.startswith("botcfg_categories:") - & (~F.data.endswith(":noop")), - ) - dp.callback_query.register( - show_bot_config_category, - F.data.startswith("botcfg_cat:"), - ) - dp.callback_query.register( - show_bot_config_setting, - F.data.startswith("botcfg_setting:"), - ) - dp.callback_query.register( - start_edit_setting, - F.data.startswith("botcfg_edit:"), - ) - dp.callback_query.register( - reset_setting, - F.data.startswith("botcfg_reset:"), - ) - dp.callback_query.register( - toggle_setting, - F.data.startswith("botcfg_toggle:"), - ) - dp.message.register( - handle_edit_setting, - BotConfigStates.waiting_for_value, - ) - diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py index f9701c6f..13c37204 100644 --- a/app/keyboards/admin.py +++ b/app/keyboards/admin.py @@ -99,9 +99,6 @@ def get_admin_settings_submenu_keyboard(language: str = "ru") -> InlineKeyboardM InlineKeyboardButton(text=texts.ADMIN_REMNAWAVE, callback_data="admin_remnawave"), InlineKeyboardButton(text=texts.ADMIN_MONITORING, callback_data="admin_monitoring") ], - [ - InlineKeyboardButton(text="🧩 Конфигурация бота", callback_data="admin_bot_config"), - ], [ InlineKeyboardButton( text=texts.t("ADMIN_MONITORING_SETTINGS", "⚙️ Настройки мониторинга"), diff --git a/app/services/system_settings_service.py b/app/services/system_settings_service.py deleted file mode 100644 index 70a9d890..00000000 --- a/app/services/system_settings_service.py +++ /dev/null @@ -1,361 +0,0 @@ -import json -import logging -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_args, get_origin - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.config import Settings, settings -from app.database.crud.system_setting import ( - delete_system_setting, - upsert_system_setting, -) -from app.database.database import AsyncSessionLocal -from app.database.models import SystemSetting - - -logger = logging.getLogger(__name__) - - -def _title_from_key(key: str) -> str: - parts = key.split("_") - if not parts: - return key - return " ".join(part.capitalize() for part in parts) - - -def _truncate(value: str, max_len: int = 60) -> str: - value = value.strip() - if len(value) <= max_len: - return value - return value[: max_len - 1] + "…" - - -@dataclass(slots=True) -class SettingDefinition: - key: str - category_key: str - category_label: str - python_type: Type[Any] - type_label: str - is_optional: bool - - @property - def display_name(self) -> str: - return _title_from_key(self.key) - - -class BotConfigurationService: - EXCLUDED_KEYS: set[str] = {"BOT_TOKEN", "ADMIN_IDS"} - - CATEGORY_TITLES: Dict[str, str] = { - "DATABASE": "База данных", - "POSTGRES": "PostgreSQL", - "SQLITE": "SQLite", - "REDIS": "Redis", - "REMNAWAVE": "Remnawave", - "SUPPORT": "Поддержка", - "ADMIN": "Администрирование", - "CHANNEL": "Каналы", - "TRIAL": "Триал", - "DEFAULT": "Значения по умолчанию", - "PRICE": "Цены", - "TRAFFIC": "Трафик", - "REFERRAL": "Реферальная программа", - "AUTOPAY": "Автопродление", - "MONITORING": "Мониторинг", - "SERVER": "Статус серверов", - "MAINTENANCE": "Техработы", - "PAYMENT": "Оплаты", - "YOOKASSA": "YooKassa", - "CRYPTOBOT": "CryptoBot", - "MULENPAY": "MulenPay", - "PAL24": "PayPalych", - "CONNECT": "Кнопка подключения", - "HAPP": "Happ", - "VERSION": "Версии", - "BACKUP": "Бекапы", - "WEBHOOK": "Вебхуки", - "LOG": "Логи", - "DEBUG": "Отладка", - "TRIBUTE": "Tribute", - "TELEGRAM": "Telegram Stars", - } - - _definitions: Dict[str, SettingDefinition] = {} - _original_values: Dict[str, Any] = settings.model_dump() - _overrides_raw: Dict[str, Optional[str]] = {} - - @classmethod - def initialize_definitions(cls) -> None: - if cls._definitions: - return - - for key, field in Settings.model_fields.items(): - if key in cls.EXCLUDED_KEYS: - continue - - annotation = field.annotation - python_type, is_optional = cls._normalize_type(annotation) - type_label = cls._type_to_label(python_type, is_optional) - - category_key = cls._resolve_category_key(key) - category_label = cls.CATEGORY_TITLES.get( - category_key, - category_key.capitalize() if category_key else "Прочее", - ) - - cls._definitions[key] = SettingDefinition( - key=key, - category_key=category_key or "other", - category_label=category_label, - python_type=python_type, - type_label=type_label, - is_optional=is_optional, - ) - - @classmethod - def _resolve_category_key(cls, key: str) -> str: - if "_" not in key: - return key.upper() - prefix = key.split("_", 1)[0] - return prefix.upper() - - @classmethod - def _normalize_type(cls, annotation: Any) -> Tuple[Type[Any], bool]: - if annotation is None: - return str, True - - origin = get_origin(annotation) - if origin is Union: - args = [arg for arg in get_args(annotation) if arg is not type(None)] - if len(args) == 1: - nested_type, nested_optional = cls._normalize_type(args[0]) - return nested_type, True - return str, True - - if annotation in {int, float, bool, str}: - return annotation, False - - if annotation in {Optional[int], Optional[float], Optional[bool], Optional[str]}: - nested = get_args(annotation)[0] - return nested, True - - # Paths, lists, dicts и прочее будем хранить как строки - return str, False - - @classmethod - def _type_to_label(cls, python_type: Type[Any], is_optional: bool) -> str: - base = { - bool: "bool", - int: "int", - float: "float", - str: "str", - }.get(python_type, "str") - return f"optional[{base}]" if is_optional else base - - @classmethod - def get_categories(cls) -> List[Tuple[str, str, int]]: - cls.initialize_definitions() - categories: Dict[str, List[SettingDefinition]] = {} - - for definition in cls._definitions.values(): - categories.setdefault(definition.category_key, []).append(definition) - - result: List[Tuple[str, str, int]] = [] - for category_key, items in categories.items(): - label = items[0].category_label - result.append((category_key, label, len(items))) - - result.sort(key=lambda item: item[1]) - return result - - @classmethod - def get_settings_for_category(cls, category_key: str) -> List[SettingDefinition]: - cls.initialize_definitions() - filtered = [ - definition - for definition in cls._definitions.values() - if definition.category_key == category_key - ] - filtered.sort(key=lambda definition: definition.key) - return filtered - - @classmethod - def get_definition(cls, key: str) -> SettingDefinition: - cls.initialize_definitions() - return cls._definitions[key] - - @classmethod - def has_override(cls, key: str) -> bool: - return key in cls._overrides_raw - - @classmethod - def get_current_value(cls, key: str) -> Any: - return getattr(settings, key) - - @classmethod - def get_original_value(cls, key: str) -> Any: - return cls._original_values.get(key) - - @classmethod - def format_value(cls, value: Any) -> str: - if value is None: - return "—" - if isinstance(value, bool): - return "✅ Да" if value else "❌ Нет" - if isinstance(value, (int, float)): - return str(value) - if isinstance(value, (list, dict, tuple, set)): - try: - return json.dumps(value, ensure_ascii=False) - except Exception: - return str(value) - return str(value) - - @classmethod - def format_value_for_list(cls, key: str) -> str: - value = cls.get_current_value(key) - formatted = cls.format_value(value) - if formatted == "—": - return formatted - return _truncate(formatted) - - @classmethod - async def initialize(cls) -> None: - cls.initialize_definitions() - - async with AsyncSessionLocal() as session: - result = await session.execute(select(SystemSetting)) - rows = result.scalars().all() - - overrides: Dict[str, Optional[str]] = {} - for row in rows: - if row.key in cls._definitions: - overrides[row.key] = row.value - - for key, raw_value in overrides.items(): - try: - parsed_value = cls.deserialize_value(key, raw_value) - except Exception as error: - logger.error("Не удалось применить настройку %s: %s", key, error) - continue - - cls._overrides_raw[key] = raw_value - cls._apply_to_settings(key, parsed_value) - - @classmethod - async def reload(cls) -> None: - cls._overrides_raw.clear() - await cls.initialize() - - @classmethod - def deserialize_value(cls, key: str, raw_value: Optional[str]) -> Any: - if raw_value is None: - return None - - definition = cls.get_definition(key) - python_type = definition.python_type - - if python_type is bool: - value_lower = raw_value.strip().lower() - if value_lower in {"1", "true", "on", "yes", "да"}: - return True - if value_lower in {"0", "false", "off", "no", "нет"}: - return False - raise ValueError(f"Неверное булево значение: {raw_value}") - - if python_type is int: - return int(raw_value) - - if python_type is float: - return float(raw_value) - - return raw_value - - @classmethod - def serialize_value(cls, key: str, value: Any) -> Optional[str]: - if value is None: - return None - - definition = cls.get_definition(key) - python_type = definition.python_type - - if python_type is bool: - return "true" if value else "false" - if python_type in {int, float}: - return str(value) - return str(value) - - @classmethod - def parse_user_value(cls, key: str, user_input: str) -> Any: - definition = cls.get_definition(key) - text = (user_input or "").strip() - - if text.lower() in {"отмена", "cancel"}: - raise ValueError("Ввод отменен пользователем") - - if definition.is_optional and text.lower() in {"none", "null", "пусто", ""}: - return None - - python_type = definition.python_type - - if python_type is bool: - lowered = text.lower() - if lowered in {"1", "true", "on", "yes", "да", "вкл", "enable", "enabled"}: - return True - if lowered in {"0", "false", "off", "no", "нет", "выкл", "disable", "disabled"}: - return False - raise ValueError("Введите 'true' или 'false' (или 'да'/'нет')") - - if python_type is int: - return int(text) - - if python_type is float: - return float(text.replace(",", ".")) - - return text - - @classmethod - async def set_value(cls, db: AsyncSession, key: str, value: Any) -> None: - raw_value = cls.serialize_value(key, value) - await upsert_system_setting(db, key, raw_value) - cls._overrides_raw[key] = raw_value - cls._apply_to_settings(key, value) - - @classmethod - async def reset_value(cls, db: AsyncSession, key: str) -> None: - await delete_system_setting(db, key) - cls._overrides_raw.pop(key, None) - original = cls.get_original_value(key) - cls._apply_to_settings(key, original) - - @classmethod - def _apply_to_settings(cls, key: str, value: Any) -> None: - try: - setattr(settings, key, value) - except Exception as error: - logger.error("Не удалось применить значение %s=%s: %s", key, value, error) - - @classmethod - def get_setting_summary(cls, key: str) -> Dict[str, Any]: - definition = cls.get_definition(key) - current = cls.get_current_value(key) - original = cls.get_original_value(key) - has_override = cls.has_override(key) - - return { - "key": key, - "name": definition.display_name, - "current": cls.format_value(current), - "original": cls.format_value(original), - "type": definition.type_label, - "category_key": definition.category_key, - "category_label": definition.category_label, - "has_override": has_override, - } - - -bot_configuration_service = BotConfigurationService - diff --git a/app/states.py b/app/states.py index 2b93a56e..f824f9a5 100644 --- a/app/states.py +++ b/app/states.py @@ -123,10 +123,6 @@ class AdminTicketStates(StatesGroup): class SupportSettingsStates(StatesGroup): waiting_for_desc = State() - -class BotConfigStates(StatesGroup): - waiting_for_value = State() - class AutoPayStates(StatesGroup): setting_autopay_days = State() confirming_autopay_toggle = State() diff --git a/main.py b/main.py index cf19d0b5..254e5c5d 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,6 @@ from app.database.universal_migration import run_universal_migration from app.services.backup_service import backup_service from app.services.reporting_service import reporting_service from app.localization.loader import ensure_locale_templates -from app.services.system_settings_service import bot_configuration_service class GracefulExit: @@ -86,13 +85,6 @@ async def main(): else: logger.info("ℹ️ Миграция пропущена (SKIP_MIGRATION=true)") - logger.info("⚙️ Загрузка конфигурации из БД...") - try: - await bot_configuration_service.initialize() - logger.info("✅ Конфигурация загружена") - except Exception as error: - logger.error(f"❌ Не удалось загрузить конфигурацию: {error}") - logger.info("🤖 Настройка бота...") bot, dp = await setup_bot()