From 87af0312cb9ff2fe3330c42395492bdc895ece06 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 14 Sep 2025 04:04:05 +0300 Subject: [PATCH 1/3] Update validators.py --- app/utils/validators.py | 104 +++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/app/utils/validators.py b/app/utils/validators.py index 79ce825e..15f1935e 100644 --- a/app/utils/validators.py +++ b/app/utils/validators.py @@ -1,5 +1,5 @@ import re -from typing import Optional, Union +from typing import Optional, Union, Tuple from datetime import datetime import html @@ -14,6 +14,11 @@ ALLOWED_HTML_TAGS = { 'blockquote' } +SELF_CLOSING_TAGS = { + 'br', 'hr', 'img' +} + + def validate_email(email: str) -> bool: pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None @@ -158,7 +163,8 @@ def validate_referral_code(code: str) -> bool: return validate_promocode(code) -def validate_html_tags(text: str) -> tuple[bool, str]: + +def validate_html_tags(text: str) -> Tuple[bool, str]: if not text: return True, "" @@ -168,21 +174,34 @@ def validate_html_tags(text: str) -> tuple[bool, str]: for is_closing, tag_name in tags: tag_name_lower = tag_name.lower() - if tag_name_lower not in ALLOWED_HTML_TAGS: + if tag_name_lower not in ALLOWED_HTML_TAGS and tag_name_lower not in SELF_CLOSING_TAGS: return False, f"Неподдерживаемый тег: <{tag_name}>" + return validate_html_structure(text) + + +def validate_html_structure(text: str) -> Tuple[bool, str]: + tag_pattern = r'<(/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*?/?>' + + matches = re.finditer(tag_pattern, text) tag_stack = [] - for is_closing, tag_name in tags: - tag_name_lower = tag_name.lower() + + for match in matches: + full_tag = match.group(0) + is_closing = bool(match.group(1)) + tag_name = match.group(2).lower() - if not is_closing: - tag_stack.append(tag_name_lower) - else: + if full_tag.endswith('/>') or tag_name in SELF_CLOSING_TAGS: + continue + + if not is_closing: + tag_stack.append(tag_name) + else: if not tag_stack: return False, f"Закрывающий тег без открывающего: " last_tag = tag_stack.pop() - if last_tag != tag_name_lower: + if last_tag != tag_name: return False, f"Неправильная вложенность тегов: ожидался , найден " if tag_stack: @@ -191,16 +210,65 @@ def validate_html_tags(text: str) -> tuple[bool, str]: return True, "" +def fix_html_tags(text: str) -> str: + if not text: + return text + + fixes = [ + (r']+)>', r''), + (r'<(br|hr|img[^>]*?)>', r'<\1 />'), + (r'<<([^>]+)>>', r'<\1>'), + (r'<\s+([^>]+)\s+>', r'<\1>'), + ] + + result = text + for pattern, replacement in fixes: + result = re.sub(pattern, replacement, result, flags=re.IGNORECASE) + + return result + + def get_html_help_text() -> str: return """Поддерживаемые HTML теги: -- <b>жирный</b> или <strong>жирный</strong> -- <i>курсив</i> или <em>курсив</em> -- <u>подчеркнутый</u> -- <s>зачеркнутый</s> -- <code>моноширинный</code> -- <pre>блок кода</pre> -- <a href="url">ссылка</a> -- <blockquote>цитата</blockquote> +• <b>жирный</b> или <strong>жирный</strong> +• <i>курсив</i> или <em>курсив</em> +• <u>подчеркнутый</u> +• <s>зачеркнутый</s> +• <code>моноширинный</code> +• <pre>блок кода</pre> +• <a href="url">ссылка</a> +• <blockquote>цитата</blockquote> -Неподдерживаемые теги: <br>, <p>, <div>, <span>, <spoiler> и другие""" +⚠️ Важные правила: +• Каждый открывающий тег должен быть закрыт +• Теги должны быть правильно вложены +• Атрибуты ссылок берите в кавычки + +❌ Неправильно: +<b>жирный <i>курсив</b></i> +<a href=google.com>ссылка</a> + +✅ Правильно: +<b>жирный <i>курсив</i></b> +<a href="https://google.com">ссылка</a>""" + + +def validate_rules_content(text: str) -> Tuple[bool, str, Optional[str]]: + if not text or not text.strip(): + return False, "Текст правил не может быть пустым", None + + if len(text) > 4000: + return False, f"Текст слишком длинный: {len(text)} символов (максимум 4000)", None + + is_valid_html, html_error = validate_html_tags(text) + if not is_valid_html: + fixed_text = fix_html_tags(text) + fixed_is_valid, _ = validate_html_tags(fixed_text) + + if fixed_is_valid and fixed_text != text: + return False, html_error, fixed_text + else: + return False, html_error, None + + return True, "", None From 80e95bc77b2864e27b7ea981ae58bb41554b7b1f Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 14 Sep 2025 04:04:31 +0300 Subject: [PATCH 2/3] Update rules.py --- app/database/crud/rules.py | 142 ++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 3 deletions(-) diff --git a/app/database/crud/rules.py b/app/database/crud/rules.py index c66fa05f..43de490d 100644 --- a/app/database/crud/rules.py +++ b/app/database/crud/rules.py @@ -1,6 +1,6 @@ import logging from typing import Optional -from sqlalchemy import select +from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime @@ -53,10 +53,37 @@ async def create_or_update_rules( await db.commit() await db.refresh(new_rules) - logger.info(f"✅ Правила для языка {language} обновлены") + logger.info(f"✅ Правила для языка {language} обновлены (ID: {new_rules.id})") return new_rules +async def clear_all_rules(db: AsyncSession, language: str = "ru") -> bool: + try: + result = await db.execute( + update(ServiceRule) + .where( + ServiceRule.language == language, + ServiceRule.is_active == True + ) + .values( + is_active=False, + updated_at=datetime.utcnow() + ) + ) + + await db.commit() + + rows_affected = result.rowcount + logger.info(f"✅ Очищены правила для языка {language}. Деактивировано записей: {rows_affected}") + + return rows_affected > 0 + + except Exception as e: + logger.error(f"❌ Ошибка при очистке правил для языка {language}: {e}") + await db.rollback() + raise + + async def get_current_rules_content(db: AsyncSession, language: str = "ru") -> str: rules = await get_rules_by_language(db, language) @@ -79,4 +106,113 @@ async def get_current_rules_content(db: AsyncSession, language: str = "ru") -> s 6. При возникновении вопросов обращайтесь в техническую поддержку. Используя сервис, вы соглашаетесь с данными правилами. -""" \ No newline at end of file +""" + + +async def get_all_rules_versions( + db: AsyncSession, + language: str = "ru", + limit: int = 10 +) -> list[ServiceRule]: + result = await db.execute( + select(ServiceRule) + .where(ServiceRule.language == language) + .order_by(ServiceRule.created_at.desc()) + .limit(limit) + ) + return result.scalars().all() + + +async def restore_rules_version( + db: AsyncSession, + rule_id: int, + language: str = "ru" +) -> Optional[ServiceRule]: + try: + result = await db.execute( + select(ServiceRule).where( + ServiceRule.id == rule_id, + ServiceRule.language == language + ) + ) + rule_to_restore = result.scalar_one_or_none() + + if not rule_to_restore: + logger.warning(f"Правило с ID {rule_id} не найдено для языка {language}") + return None + + await db.execute( + update(ServiceRule) + .where( + ServiceRule.language == language, + ServiceRule.is_active == True + ) + .values( + is_active=False, + updated_at=datetime.utcnow() + ) + ) + + restored_rule = ServiceRule( + title=rule_to_restore.title, + content=rule_to_restore.content, + language=language, + is_active=True, + order=0 + ) + + db.add(restored_rule) + await db.commit() + await db.refresh(restored_rule) + + logger.info(f"✅ Восстановлена версия правил ID {rule_id} как новое правило ID {restored_rule.id}") + return restored_rule + + except Exception as e: + logger.error(f"❌ Ошибка при восстановлении правил ID {rule_id}: {e}") + await db.rollback() + raise + + +async def get_rules_statistics(db: AsyncSession) -> dict: + try: + active_result = await db.execute( + select(ServiceRule).where(ServiceRule.is_active == True) + ) + active_rules = active_result.scalars().all() + + all_result = await db.execute(select(ServiceRule)) + all_rules = all_result.scalars().all() + + languages_stats = {} + for rule in active_rules: + lang = rule.language + if lang not in languages_stats: + languages_stats[lang] = { + 'active_count': 0, + 'last_updated': None, + 'content_length': 0 + } + + languages_stats[lang]['active_count'] += 1 + languages_stats[lang]['content_length'] = len(rule.content) + + if not languages_stats[lang]['last_updated'] or rule.updated_at > languages_stats[lang]['last_updated']: + languages_stats[lang]['last_updated'] = rule.updated_at + + return { + 'total_active': len(active_rules), + 'total_all_time': len(all_rules), + 'languages': languages_stats, + 'total_languages': len(languages_stats) + } + + except Exception as e: + logger.error(f"❌ Ошибка при получении статистики правил: {e}") + return { + 'total_active': 0, + 'total_all_time': 0, + 'languages': {}, + 'total_languages': 0, + 'error': str(e) + } From b8a1aa3f3c0e9df0c6bc4dfc154f74ce81c6ac05 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 14 Sep 2025 04:05:02 +0300 Subject: [PATCH 3/3] Add files via upload --- app/handlers/admin/main.py | 133 ++++++++++++++++++ app/handlers/admin/rules.py | 265 ++++++++++++++++++++++++++++++------ 2 files changed, 356 insertions(+), 42 deletions(-) diff --git a/app/handlers/admin/main.py b/app/handlers/admin/main.py index ee9dbc02..cc129acb 100644 --- a/app/handlers/admin/main.py +++ b/app/handlers/admin/main.py @@ -1,5 +1,6 @@ import logging from aiogram import Dispatcher, types, F +from aiogram.filters import Command from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings @@ -14,6 +15,8 @@ from app.keyboards.admin import ( ) from app.localization.texts import get_texts from app.utils.decorators import admin_required, error_handler +from app.database.crud.rules import clear_all_rules, get_rules_statistics +from app.localization.texts import clear_rules_cache logger = logging.getLogger(__name__) @@ -145,6 +148,121 @@ async def show_system_submenu( await callback.answer() + +@admin_required +@error_handler +async def clear_rules_command( + message: types.Message, + db_user: User, + db: AsyncSession +): + try: + stats = await get_rules_statistics(db) + + if stats['total_active'] == 0: + await message.reply( + "ℹ️ Правила уже очищены\n\n" + "В системе нет активных правил. Используются стандартные правила по умолчанию." + ) + return + + success = await clear_all_rules(db, db_user.language) + + if success: + clear_rules_cache() + + await message.reply( + f"✅ Правила успешно очищены!\n\n" + f"📊 Статистика:\n" + f"• Очищено правил: {stats['total_active']}\n" + f"• Язык: {db_user.language}\n" + f"• Выполнил: {db_user.full_name}\n\n" + f"Теперь используются стандартные правила по умолчанию." + ) + + logger.info(f"Правила очищены командой администратором {db_user.telegram_id} ({db_user.full_name})") + else: + await message.reply( + "⚠️ Нет правил для очистки\n\n" + "Активные правила не найдены." + ) + + except Exception as e: + logger.error(f"Ошибка при очистке правил командой: {e}") + await message.reply( + "❌ Ошибка при очистке правил\n\n" + f"Произошла ошибка: {str(e)}\n" + "Попробуйте через админ-панель или повторите позже." + ) + + +@admin_required +@error_handler +async def rules_stats_command( + message: types.Message, + db_user: User, + db: AsyncSession +): + try: + stats = await get_rules_statistics(db) + + if 'error' in stats: + await message.reply(f"❌ Ошибка получения статистики: {stats['error']}") + return + + text = f"📊 Статистика правил сервиса\n\n" + text += f"📋 Общая информация:\n" + text += f"• Активных правил: {stats['total_active']}\n" + text += f"• Всего в истории: {stats['total_all_time']}\n" + text += f"• Поддерживаемых языков: {stats['total_languages']}\n\n" + + if stats['languages']: + text += f"🌐 По языкам:\n" + for lang, lang_stats in stats['languages'].items(): + text += f"• {lang}: {lang_stats['active_count']} правил, " + text += f"{lang_stats['content_length']} символов\n" + if lang_stats['last_updated']: + text += f" Обновлено: {lang_stats['last_updated'].strftime('%d.%m.%Y %H:%M')}\n" + else: + text += "ℹ️ Активных правил нет - используются правила по умолчанию" + + await message.reply(text) + + except Exception as e: + logger.error(f"Ошибка при получении статистики правил: {e}") + await message.reply( + f"❌ Ошибка получения статистики\n\n" + f"Произошла ошибка: {str(e)}" + ) + + +@admin_required +@error_handler +async def admin_commands_help( + message: types.Message, + db_user: User, + db: AsyncSession +): + help_text = """ +🔧 Доступные админские команды: + +📋 Управление правилами: +• /clear_rules - очистить все правила +• /rules_stats - статистика правил + +ℹ️ Справка: +• /admin_help - это сообщение + +📱 Панель управления: +Используйте кнопку "Админ панель" в главном меню для полного доступа ко всем функциям. + +⚠️ Важно: +Все команды логируются и требуют админских прав. +""" + + await message.reply(help_text) + + def register_handlers(dp: Dispatcher): dp.callback_query.register( show_admin_panel, @@ -175,3 +293,18 @@ def register_handlers(dp: Dispatcher): show_system_submenu, F.data == "admin_submenu_system" ) + + dp.message.register( + clear_rules_command, + Command("clear_rules") + ) + + dp.message.register( + rules_stats_command, + Command("rules_stats") + ) + + dp.message.register( + admin_commands_help, + Command("admin_help") + ) \ No newline at end of file diff --git a/app/handlers/admin/rules.py b/app/handlers/admin/rules.py index 3b224338..b449c627 100644 --- a/app/handlers/admin/rules.py +++ b/app/handlers/admin/rules.py @@ -8,7 +8,8 @@ from app.states import AdminStates from app.database.models import User from app.localization.texts import get_texts from app.utils.decorators import admin_required, error_handler -from app.database.crud.rules import get_current_rules_content, create_or_update_rules +from app.utils.validators import validate_html_tags, get_html_help_text +from app.database.crud.rules import get_current_rules_content, create_or_update_rules, clear_all_rules logger = logging.getLogger(__name__) @@ -31,7 +32,9 @@ async def show_rules_management( keyboard = [ [types.InlineKeyboardButton(text="📝 Редактировать правила", callback_data="admin_edit_rules")], [types.InlineKeyboardButton(text="👀 Просмотр правил", callback_data="admin_view_rules")], - [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_panel")] + [types.InlineKeyboardButton(text="🗑️ Очистить правила", callback_data="admin_clear_rules")], + [types.InlineKeyboardButton(text="ℹ️ Помощь по HTML", callback_data="admin_rules_help")], + [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_submenu_settings")] ] await callback.message.edit_text( @@ -48,16 +51,33 @@ async def view_current_rules( db_user: User, db: AsyncSession ): - current_rules = await get_current_rules_content(db, db_user.language) - - await callback.message.edit_text( - f"📋 Текущие правила сервиса\n\n{current_rules}", - reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ - [types.InlineKeyboardButton(text="✏️ Редактировать", callback_data="admin_edit_rules")], - [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_rules")] - ]) - ) - await callback.answer() + try: + current_rules = await get_current_rules_content(db, db_user.language) + + is_valid, error_msg = validate_html_tags(current_rules) + warning = "" + if not is_valid: + warning = f"\n\n⚠️ Внимание: В правилах найдена ошибка HTML: {error_msg}" + + await callback.message.edit_text( + f"📋 Текущие правила сервиса\n\n{current_rules}{warning}", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="✏️ Редактировать", callback_data="admin_edit_rules")], + [types.InlineKeyboardButton(text="🗑️ Очистить", callback_data="admin_clear_rules")], + [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_rules")] + ]) + ) + await callback.answer() + except Exception as e: + logger.error(f"Ошибка при показе правил: {e}") + await callback.message.edit_text( + "❌ Ошибка при загрузке правил. Возможно, в тексте есть некорректные HTML теги.", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="🗑️ Очистить правила", callback_data="admin_clear_rules")], + [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_rules")] + ]) + ) + await callback.answer() @admin_required @@ -68,20 +88,33 @@ async def start_edit_rules( state: FSMContext, db: AsyncSession ): - current_rules = await get_current_rules_content(db, db_user.language) - - await callback.message.edit_text( - "✏️ Редактирование правил\n\n" - f"Текущие правила:\n{current_rules[:500]}{'...' if len(current_rules) > 500 else ''}\n\n" - "Отправьте новый текст правил сервиса.\n\n" - "Поддерживается HTML разметка", - reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ - [types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules")] - ]) - ) - - await state.set_state(AdminStates.editing_rules_page) - await callback.answer() + try: + current_rules = await get_current_rules_content(db, db_user.language) + + preview = current_rules[:500] + ('...' if len(current_rules) > 500 else '') + + text = ( + "✏️ Редактирование правил\n\n" + f"Текущие правила:\n{preview}\n\n" + "Отправьте новый текст правил сервиса.\n\n" + "Поддерживается HTML разметка. Все теги будут проверены перед сохранением.\n\n" + "💡 Совет: Нажмите /html_help для просмотра поддерживаемых тегов" + ) + + await callback.message.edit_text( + text, + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="ℹ️ HTML помощь", callback_data="admin_rules_help")], + [types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules")] + ]) + ) + + await state.set_state(AdminStates.editing_rules_page) + await callback.answer() + + except Exception as e: + logger.error(f"Ошибка при начале редактирования правил: {e}") + await callback.answer("❌ Ошибка при загрузке правил для редактирования", show_alert=True) @admin_required @@ -98,19 +131,61 @@ async def process_rules_edit( await message.answer("❌ Текст правил слишком длинный (максимум 4000 символов)") return - await message.answer( - f"📋 Предварительный просмотр новых правил:\n\n{new_rules}\n\n" - f"⚠️ Внимание! Новые правила будут показываться всем пользователям.\n\n" - f"Сохранить изменения?", - reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ - [ - types.InlineKeyboardButton(text="✅ Сохранить", callback_data="admin_save_rules"), - types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules") - ] - ]) - ) + is_valid, error_msg = validate_html_tags(new_rules) + if not is_valid: + await message.answer( + f"❌ Ошибка в HTML разметке:\n{error_msg}\n\n" + f"Пожалуйста, исправьте ошибки и отправьте текст заново.\n\n" + f"💡 Используйте /html_help для просмотра правильного синтаксиса", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="ℹ️ HTML помощь", callback_data="admin_rules_help")], + [types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules")] + ]) + ) + return - await state.update_data(new_rules=new_rules) + try: + preview_text = f"📋 Предварительный просмотр новых правил:\n\n{new_rules}\n\n" + preview_text += f"⚠️ Внимание! Новые правила будут показываться всем пользователям.\n\n" + preview_text += f"Сохранить изменения?" + + if len(preview_text) > 4000: + preview_text = ( + "📋 Предварительный просмотр новых правил:\n\n" + f"{new_rules[:500]}...\n\n" + f"⚠️ Внимание! Новые правила будут показываться всем пользователям.\n\n" + f"Текст правил: {len(new_rules)} символов\n" + f"Сохранить изменения?" + ) + + await message.answer( + preview_text, + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [ + types.InlineKeyboardButton(text="✅ Сохранить", callback_data="admin_save_rules"), + types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules") + ] + ]) + ) + + await state.update_data(new_rules=new_rules) + + except Exception as e: + logger.error(f"Ошибка при показе превью правил: {e}") + await message.answer( + "⚠️ Подтверждение сохранения правил\n\n" + f"Новые правила готовы к сохранению ({len(new_rules)} символов).\n" + f"HTML теги проверены и корректны.\n\n" + f"Сохранить изменения?", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [ + types.InlineKeyboardButton(text="✅ Сохранить", callback_data="admin_save_rules"), + types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules") + ] + ]) + ) + + await state.update_data(new_rules=new_rules) @admin_required @@ -128,6 +203,20 @@ async def save_rules( await callback.answer("❌ Ошибка: текст правил не найден", show_alert=True) return + is_valid, error_msg = validate_html_tags(new_rules) + if not is_valid: + await callback.message.edit_text( + f"❌ Ошибка при сохранении:\n{error_msg}\n\n" + f"Правила не были сохранены из-за ошибок в HTML разметке.", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="admin_edit_rules")], + [types.InlineKeyboardButton(text="📋 К правилам", callback_data="admin_rules")] + ]) + ) + await state.clear() + await callback.answer() + return + try: await create_or_update_rules( db=db, @@ -142,10 +231,14 @@ async def save_rules( await refresh_rules_cache(db_user.language) await callback.message.edit_text( - "✅ Правила сервиса обновлены!\n\n" - "Новые правила сохранены в базе данных и будут показываться пользователям.\n\n" - "Кеш правил очищен и обновлен.", + "✅ Правила сервиса успешно обновлены!\n\n" + "✓ Новые правила сохранены в базе данных\n" + "✓ HTML теги проверены и корректны\n" + "✓ Кеш правил очищен и обновлен\n" + "✓ Правила будут показываться пользователям\n\n" + f"📊 Размер текста: {len(new_rules)} символов", reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="👀 Просмотреть", callback_data="admin_view_rules")], [types.InlineKeyboardButton(text="📋 К правилам", callback_data="admin_rules")] ]) ) @@ -156,7 +249,90 @@ async def save_rules( except Exception as e: logger.error(f"Ошибка сохранения правил: {e}") - await callback.answer("❌ Ошибка сохранения правил", show_alert=True) + await callback.message.edit_text( + "❌ Ошибка при сохранении правил\n\n" + "Произошла ошибка при записи в базу данных. Попробуйте еще раз.", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="admin_save_rules")], + [types.InlineKeyboardButton(text="📋 К правилам", callback_data="admin_rules")] + ]) + ) + await callback.answer() + + +@admin_required +@error_handler +async def clear_rules_confirmation( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + await callback.message.edit_text( + "🗑️ Очистка правил сервиса\n\n" + "⚠️ ВНИМАНИЕ! Вы собираетесь полностью удалить все правила сервиса.\n\n" + "После очистки пользователи будут видеть стандартные правила по умолчанию.\n\n" + "Это действие нельзя отменить. Продолжить?", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [ + types.InlineKeyboardButton(text="✅ Да, очистить", callback_data="admin_confirm_clear_rules"), + types.InlineKeyboardButton(text="❌ Отмена", callback_data="admin_rules") + ] + ]) + ) + await callback.answer() + + +@admin_required +@error_handler +async def confirm_clear_rules( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + try: + await clear_all_rules(db, db_user.language) + + from app.localization.texts import clear_rules_cache + clear_rules_cache() + + await callback.message.edit_text( + "✅ Правила успешно очищены!\n\n" + "✓ Все пользовательские правила удалены\n" + "✓ Теперь используются стандартные правила\n" + "✓ Кеш правил очищен\n\n" + "Пользователи будут видеть правила по умолчанию.", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="📝 Создать новые", callback_data="admin_edit_rules")], + [types.InlineKeyboardButton(text="👀 Посмотреть текущие", callback_data="admin_view_rules")], + [types.InlineKeyboardButton(text="📋 К правилам", callback_data="admin_rules")] + ]) + ) + + logger.info(f"Правила очищены администратором {db_user.telegram_id}") + await callback.answer() + + except Exception as e: + logger.error(f"Ошибка при очистке правил: {e}") + await callback.answer("❌ Ошибка при очистке правил", show_alert=True) + + +@admin_required +@error_handler +async def show_html_help( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + help_text = get_html_help_text() + + await callback.message.edit_text( + f"ℹ️ Справка по HTML форматированию\n\n{help_text}", + reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text="📝 Редактировать правила", callback_data="admin_edit_rules")], + [types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_rules")] + ]) + ) + await callback.answer() def register_handlers(dp: Dispatcher): @@ -165,4 +341,9 @@ def register_handlers(dp: Dispatcher): dp.callback_query.register(start_edit_rules, F.data == "admin_edit_rules") dp.callback_query.register(save_rules, F.data == "admin_save_rules") + dp.callback_query.register(clear_rules_confirmation, F.data == "admin_clear_rules") + dp.callback_query.register(confirm_clear_rules, F.data == "admin_confirm_clear_rules") + + dp.callback_query.register(show_html_help, F.data == "admin_rules_help") + dp.message.register(process_rules_edit, AdminStates.editing_rules_page) \ No newline at end of file