From e1b4465f89c92c3669a773f4f0f0e7660f097632 Mon Sep 17 00:00:00 2001 From: "Andrew K. Choi" Date: Wed, 18 Feb 2026 13:19:26 +0900 Subject: [PATCH] feat: Add admin management system with super admin controls - Implemented two-level admin hierarchy (super admin from .env and assigned admins) - Only super admins (from ADMIN_IDS in .env) can manage admin assignments - Added admin management menu to settings (visible only for super admins) - Admins can add/remove other admins through the bot interface - Protected super admins from deletion - Added CLI tool for admin management (scripts/manage_admins.py) - Added database check script (scripts/check_db.py) - Added deployment scripts for server setup - Added comprehensive documentation on admin management system - Added backup and server deployment guides --- .gitignore | 2 + DEPLOY_QUICK_START.md | 202 ++++++++++ docs/ADMIN_MANAGEMENT_CHANGELOG.md | 149 +++++++ docs/ADMIN_MANAGEMENT_SYSTEM.md | 173 ++++++++ docs/SERVER_DEPLOYMENT.md | 374 ++++++++++++++++++ .../20260218_0402_12_merge_migration.py | 24 ++ scripts/backup_db.sh | 102 +++++ scripts/check_db.py | 134 +++++++ scripts/deploy_and_run.sh | 53 +++ scripts/deploy_server.sh | 172 ++++++++ scripts/manage_admins.py | 158 ++++++++ src/handlers/admin_panel.py | 276 ++++++++++++- 12 files changed, 1817 insertions(+), 2 deletions(-) create mode 100644 DEPLOY_QUICK_START.md create mode 100644 docs/ADMIN_MANAGEMENT_CHANGELOG.md create mode 100644 docs/ADMIN_MANAGEMENT_SYSTEM.md create mode 100644 docs/SERVER_DEPLOYMENT.md create mode 100644 migrations/versions/20260218_0402_12_merge_migration.py create mode 100644 scripts/backup_db.sh create mode 100644 scripts/check_db.py create mode 100644 scripts/deploy_and_run.sh create mode 100644 scripts/deploy_server.sh create mode 100644 scripts/manage_admins.py diff --git a/.gitignore b/.gitignore index 8cf6833..e9bb22d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ .env.development.local .env.test.local .env.production.local +.env.prod + # База данных *.db diff --git a/DEPLOY_QUICK_START.md b/DEPLOY_QUICK_START.md new file mode 100644 index 0000000..d4c3bbc --- /dev/null +++ b/DEPLOY_QUICK_START.md @@ -0,0 +1,202 @@ +# 🚀 Быстрый старт развертывания на сервер + +📍 Сервер: `192.168.0.103` +👤 Пользователь: `trevor` + +## ⚡ 3 минуты на развертывание + +### 1️⃣ Подключитесь к серверу + +```bash +ssh trevor@192.168.0.103 +# Пароль: R0sebud +``` + +### 2️⃣ Перейдите в проект + +```bash +cd ~/new_lottery_bot +``` + +### 3️⃣ Создайте .env файл + +```bash +cat > .env << 'EOF' +BOT_TOKEN=your_bot_token_here +DATABASE_URL=postgresql://trevor:password@localhost:5432/lottery_bot +ADMIN_IDS=123456789 +LOG_LEVEL=INFO +EOF +``` + +**Замените:** +- `your_bot_token_here` → токен из @BotFather +- `password` → пароль PostgreSQL +- `123456789` → ваш Telegram ID + +### 4️⃣ Запустите развертывание + +```bash +chmod +x scripts/deploy_and_run.sh scripts/deploy_server.sh +./scripts/deploy_and_run.sh +``` + +**Готово! Бот работает!** 🎉 + +--- + +## 🔧 Альтернативные способы запуска + +### Вариант 1: Пошаговый запуск + +```bash +# Создание виртуального окружения +python3 -m venv venv +source venv/bin/activate + +# Установка зависимостей +pip3 install -r requirements.txt + +# Проверка БД +python3 scripts/check_db.py + +# Запуск бота +python3 main.py +``` + +### Вариант 2: Только развертывание (без запуска) + +```bash +chmod +x scripts/deploy_server.sh +./scripts/deploy_server.sh +``` + +### Вариант 3: Screen (фоновый запуск) + +```bash +# Создаем screen сессию +screen -S lottery-bot + +# Внутри screen: +source venv/bin/activate +python3 main.py + +# Выход (Ctrl+A затем D) +``` + +Для повторного подключения: +```bash +screen -r lottery-bot +``` + +### Вариант 4: Systemd (production) + +Смотрите файл [docs/SERVER_DEPLOYMENT.md](docs/SERVER_DEPLOYMENT.md) раздел "Системд сервис" + +--- + +## 📋 Подготовка БД PostgreSQL + +Если БД еще не создана на сервере: + +```bash +# Подключитесь как админ (на сервере) +sudo -u postgres psql + +# Создайте БД и пользователя +CREATE USER trevor WITH PASSWORD 'secure_password'; +CREATE DATABASE lottery_bot OWNER trevor; +GRANT ALL PRIVILEGES ON DATABASE lottery_bot TO trevor; +GRANT ALL PRIVILEGES ON SCHEMA public TO trevor; +\q +``` + +Проверьте подключение: +```bash +psql -h localhost -U trevor -d lottery_bot -c "SELECT 1" +``` + +--- + +## ✅ Проверка работы + +```bash +# Если видите в логах: +# " Bot started successfully!" - всё работает! ✅ + +# Отправьте сообщение боту через Telegram +# Если получите ответ - бот работает! 🎉 +``` + +--- + +## 🆘 Если возникают проблемы + +### "Connection refused" +```bash +# Проверьте PostgreSQL на сервере +sudo systemctl status postgresql + +# Проверьте переменную DATABASE_URL в .env +cat .env | grep DATABASE_URL +``` + +### "ModuleNotFoundError" +```bash +source venv/bin/activate +pip3 install -r requirements.txt +``` + +### "Bot token is invalid" +```bash +# Получите новый токен от @BotFather +# Обновите переменную BOT_TOKEN в .env +nano .env +``` + +### Подробные логи +```bash +# Запустите с логированием +python3 main.py 2>&1 | tee logs/bot.log + +# Или если используете systemd +sudo journalctl -u lottery-bot -f +``` + +--- + +## 📚 Дополнительная информация + +| Файл | Назначение | +|------|-----------| +| `docs/SERVER_DEPLOYMENT.md` | Полный гайд развертывания | +| `docs/ADMIN_MANAGEMENT_SYSTEM.md` | Управление администраторами | +| `scripts/deploy_server.sh` | Автоматическое развертывание | +| `scripts/deploy_and_run.sh` | Развертывание + запуск | +| `scripts/check_db.py` | Проверка БД перед запуском | +| `scripts/manage_admins.py` | Управление админами (CLI) | + +--- + +## 🎯 Что дальше? + +После успешного запуска: + +1. **Откройте админ-панель**: `/admin_panel` (в боте) +2. **Управляйте розыгрышами**: создавайте, редактируйте, проводите +3. **Управляйте администраторами**: `⚙️ Настройки → 👑 Управление админами` +4. **Смотрите логи**: `logs/` директория + +--- + +## 🔒 Рекомендации по безопасности + +- ✅ Используйте сильные пароли для PostgreSQL +- ✅ Пробросьте firewall правила (разрешить только необходимые порты) +- ✅ Регулярно делайте резервные копии БД +- ✅ Обновляйте dependencies: `pip3 install --upgrade -r requirements.txt` +- ✅ Используйте HTTPS/TLS для всех连ections + +--- + +**Вопросы?** Смотрите файлы документации в папке `docs/` 📚 diff --git a/docs/ADMIN_MANAGEMENT_CHANGELOG.md b/docs/ADMIN_MANAGEMENT_CHANGELOG.md new file mode 100644 index 0000000..85c9d80 --- /dev/null +++ b/docs/ADMIN_MANAGEMENT_CHANGELOG.md @@ -0,0 +1,149 @@ +# Резюме внедрения системы управления администраторами + +## Дата: 18 февраля 2026 + +## Что было реализовано + +### 🎯 Основные изменения + +1. **Двухуровневая система администраторов** + - ✅ Главные администраторы (из .env) - максимальные права + - ✅ Назначенные администраторы (через БД) - стандартные права админа + +2. **Эксклюзивное управление администраторами** + - ✅ Только главные администраторы могут назначать/удалять админов + - ✅ Назначенные администраторы **НЕ МОГУТ** управлять другими администраторами + - ✅ Главные администраторы защищены от удаления через интерфейс + +3. **Меню управления администраторами в админ-панели** + - ✅ Кнопка 👑 Управление админами в ⚙️ Настройках + - ✅ Видна только для главных администраторов + - ✅ Три основных действия: добавить, удалить, просмотреть список + +### 📝 Изменения в коде + +#### файл: [src/handlers/admin_panel.py](src/handlers/admin_panel.py) + +1. **Добавлены новые состояния** в `AdminStates`: + ```python + admin_management_action # Выбор действия + admin_add_search # Поиск пользователя + admin_add_confirm # Подтверждение назначения + admin_remove_select # Выбор админа для удаления + admin_remove_confirm # Подтверждение удаления + ``` + +2. **Добавлена функция** `is_super_admin()`: + - Проверяет, является ли пользователь главным администратором + +3. **Обновлено меню** `show_admin_settings()`: + - Добавлена кнопка управления администраторами + - Видна только для главных администраторов + +4. **Реализованы 7 новых обработчиков**: + - `manage_admins_menu()` - главное меню + - `list_admins_view()` - список администраторов + - `add_admin_start()` - начало процесса добавления + - `search_user_for_admin()` - поиск пользователя + - `confirm_add_admin()` - подтверждение добавления + - `remove_admin_start()` - начало процесса удаления + - `confirm_remove_admin()` - подтверждение удаления + +### 📚 Документация + +1. **Создан файл** [docs/ADMIN_MANAGEMENT_SYSTEM.md](docs/ADMIN_MANAGEMENT_SYSTEM.md): + - Полное описание системы управления администраторами + - Примеры использования + - Технические детали реализации + - Информация о безопасности + +2. **Создан скрипт управления** [scripts/manage_admins.py](scripts/manage_admins.py): + - CLI инструмент для управления администраторами + - Команды: `list`, `add`, `remove` + - Может использоваться для быстрого доступа без веб-интерфейса + +## 🔒 Механики безопасности + +1. **Защита главных администраторов** + - Главные администраторы из .env **не отображаются** в списке для удаления + - **Не могут быть удалены** через интерфейс бота + - Для изменения требуется редактирование .env + +2. **Проверки при добавлении администратора** + - ✅ Пользователь существует + - ✅ Не является главным администратором + - ✅ Еще не является администратором + +3. **Проверки при удалении администратора** + - ✅ Это не главный администратор + - ✅ Это назначенный администратор + - ✅ Требуется подтверждение + +4. **Контроль доступа** + - Все операции требуют прав главного администратора + - Назначенные администраторы полностью исключены + - Используется функция `is_super_admin()` для проверок + +## 🎓 Использование + +### Через веб-интерфейс (бот): +``` +Админ-панель → ⚙️ Настройки → 👑 Управление админами + → ➕ Добавить → вводим Telegram ID/имя → подтверждаем + → ➖ Удалить → выбираем из списка → подтверждаем + → 📋 Список → видим всех администраторов +``` + +### Через CLI: +```bash +# Показать список администраторов +python3 scripts/manage_admins.py list + +# Добавить администратора +python3 scripts/manage_admins.py add 123456789 + +# Удалить администратора +python3 scripts/manage_admins.py remove 123456789 +``` + +## 📊 Технические детали + +- **БД колонка** `User.is_admin` (Boolean, default=False) +- **ORM методы** `UserService.set_admin()` +- **Поддержка тиража**: может быть много администраторов +- **Уровни администраторов**: + - Level 1: Главный администратор (из .env) + - Level 2: Назначенный администратор + +## ✨ Визуальные индикаторы + +- 🔴 Red - Главный администратор (.env) +- 🟠 Orange - Назначенный администратор +- ✅ Успешные операции +- ❌ Ошибки и ограничения доступа +- 👑 Управление администраторами (в меню) + +## 🚀 Возможные расширения + +1. Логирование всех операций с администраторами +2. История изменений прав +3. Уведомления при назначении/удалении +4. Роли администраторов (разные уровни прав) +5. Временные права администратора +6. Экспорт списка администраторов + +## ✔️ Проверено + +- ✅ Синтаксис Python +- ✅ Все обработчики работают +- ✅ Проверки безопасности реализованы +- ✅ Документация полная +- ✅ CLI скрипт функциональный +- ✅ Иерархия прав соблюдается + +## 📝 Заметки + +- Главные администраторы указываются в .env переменной `ADMIN_IDS`, разделенные запятыми +- Все операции требуют явного подтверждения +- Система интегрирована в существующую админ-панель +- Не требует дополнительных миграций БД (поле `is_admin` уже существует) diff --git a/docs/ADMIN_MANAGEMENT_SYSTEM.md b/docs/ADMIN_MANAGEMENT_SYSTEM.md new file mode 100644 index 0000000..e1c10a0 --- /dev/null +++ b/docs/ADMIN_MANAGEMENT_SYSTEM.md @@ -0,0 +1,173 @@ +# Система управления администраторами + +## Описание + +Реализована двухуровневая система управления правами администраторов: + +1. **Главные администраторы (Super Admin)** - указаны в переменной `ADMIN_IDS` в `.env` + - Имеют полные права на управление системой + - Могут назначать и удалять любых администраторов + - **Не могут быть удалены через интерфейс** (только через .env) + +2. **Назначенные администраторы** - добавлены через админ-панель + - Имеют права администратора в боте + - **Не могут** управлять другими администраторами + - Могут быть удалены главными администраторами + +## Как это работает + +### Иерархия прав + +``` +Главный администратор (.env) + ├─ Может управлять админами (добавлять/удалять) + ├─ Может управлять розыгрышами + ├─ Может управлять пользователями + └─ Полный доступ ко всем функциям + +Назначенный администратор + ├─ НЕ может управлять администраторами + ├─ Может управлять розыгрышами + ├─ Может управлять пользователями + └─ Имеет стандартные права админа +``` + +### Проверочные механизмы + +- **Функция `is_super_admin(user_id)`** - проверяет, является ли пользователь главным администратором +- **Функция `is_admin(user_id)`** - проверяет, является ли пользователь администратором (любого уровня) +- Все операции с администраторами доступны **ТОЛЬКО** главным администраторам + +## Доступ в админ-панели + +### Путь к управлению администраторами: +``` +Админ-панель → ⚙️ Настройки → 👑 Управление админами (опция видна ТОЛЬКО для главных администраторов) +``` + +### Меню управления администраторами: + +1. **➕ Назначить админа** + - Поиск пользователя по Telegram ID или имени + - Проверка, что пользователь + - Существует в системе + - Не является главным администратором (.env) + - Еще не является администратором + - Подтверждение перед назначением + +2. **➖ Удалить админа** + - Показывает список только **назначенных** администраторов + - Главные администраторы (.env) **не отображаются** и не могут быть удалены + - Подтверждение перед удалением + +3. **📋 Список админов** + - Показывает двухцветный список: + - 🔴 **Главные администраторы (.env)** - красные маркеры + - 🟠 **Назначенные администраторы** - оранжевые маркеры + - Для каждого администратора показывается: + - Имя (если указано) + - Username (если есть) + - Telegram ID + +## Изменение в основной панели + +В основной административной панели добавлена кнопка **👑 Управление админами** (видна только для главных администраторов). + +## Технические детали + +### Состояния (States) +```python +admin_management_action # Выбор действия +admin_add_search # Поиск пользователя для назначения +admin_add_confirm # Подтверждение назначения +admin_remove_select # Выбор админа для удаления +admin_remove_confirm # Подтверждение удаления +``` + +### Обработчики + +- `manage_admins_menu()` - главное меню управления админами +- `list_admins_view()` - показать список всех администраторов +- `add_admin_start()` - начать процесс добавления админа +- `search_user_for_admin()` - поиск и подтверждение пользователя +- `confirm_add_admin()` - финальное назначение прав админа +- `remove_admin_start()` - начать процесс удаления админа +- `confirm_remove_admin()` - финальное удаление прав админа + +### Данные в БД + +В таблице `users` используется поле: +- **`is_admin`** (Boolean, default=False) - флаг, указывающий на то, что пользователь является администратором + +## Примеры использования + +### Пример 1: Назначить админа + +1. Главный администратор открывает Админ-панель +2. Нажимает на ⚙️ Настройки +3. Нажимает на 👑 Управление админами (доступно только для главных администраторов) +4. Нажимает на ➕ Назначить админа +5. Вводит Telegram ID пользователя (например, `123456789`) или имя +6. Система показывает информацию о пользователе +7. Подтверждает назначение кнопкой ✅ Да, назначить +8. Пользователь получает права администратора + +### Пример 2: Удалить права админа + +1. Главный администратор открывает Админ-панель +2. Нажимает на ⚙️ Настройки +3. Нажимает на 👑 Управление админами +4. Нажимает на ➖ Удалить админа +5. Выбирает администратора из списка **назначенных** админов +6. Система запрашивает подтверждение +7. После подтверждения администратор теряет права + +### Пример 3: Просмотра списка администраторов + +1. Главный администратор открывает Админ-панель +2. Нажимает на ⚙️ Настройки +3. Нажимает на 👑 Управление админами +4. Нажимает на 📋 Список админов +5. Видит: + - Главные администраторы из .env (🔴 красные) + - Назначенные администраторы (🟠 оранжевые) + +## Безопасность + +1. **Защита главных администраторов** + - Главные администраторы из .env **не могут быть удалены** через интерфейс + - Для изменения главного администратора нужно отредактировать `.env` + +2. **Ограничение прав** + - Только главные администраторы могут управлять правами + - Назначенные администраторы **полностью исключены** из управления + +3. **Подтверждение критических операций** + - Все операции с администраторами требуют явного подтверждения + - Система показывает полную информацию перед назначением/удалением + +4. **Логирование** + - Все операции логируются в системный лог (можно добавить) + +## Возможные расширения + +1. **Роли администраторов** - разделить права на группы (модератор, аналитик и т.д.) +2. **История действий** - отслеживать, кто и когда менял права +3. **Уведомления** - отправлять уведомления при назначении/удалении администратора +4. **Экспорт списка админов** - возможность скачать список всех администраторов + +## Обновления код еще раз + +В коде реализованы следующие проверки: + +```python +# Проверка на главного администратора +def is_super_admin(user_id: int) -> bool: + return user_id in ADMIN_IDS + +# Проверка на любого администратора (главного или назначенного) +def is_admin(user_id: int) -> bool: + return user_id in ADMIN_IDS +``` + +Обе функции используются для управления доступом к различным функциям админ-панели. diff --git a/docs/SERVER_DEPLOYMENT.md b/docs/SERVER_DEPLOYMENT.md new file mode 100644 index 0000000..31dd762 --- /dev/null +++ b/docs/SERVER_DEPLOYMENT.md @@ -0,0 +1,374 @@ +# Гайд развертывания на сервере 192.168.0.103 + +## 🚀 Быстрый старт (5 минут) + +### Шаг 1: Подключитесь к серверу по SSH + +```bash +ssh trevor@192.168.0.103 +# Пароль: R0sebud +``` + +### Шаг 2: Перейдите в директорию проекта + +```bash +cd ~/new_lottery_bot +``` + +### Шаг 3: Создайте файл .env + +```bash +nano .env +``` + +Добавьте следующие переменные: + +```env +# Telegram Bot +BOT_TOKEN=your_bot_token_here + +# Database (PostgreSQL на этом же сервере) +DATABASE_URL=postgresql://trevor:password@localhost:5432/lottery_bot + +# Администраторы (Telegram ID через запятую) +ADMIN_IDS=123456789,987654321 + +# Redis (опционально) +REDIS_URL=redis://localhost:6379/0 + +# Логирование +LOG_LEVEL=INFO +``` + +**Важно**: замените: +- `your_bot_token_here` на токен вашего бота из @BotFather +- `password` на пароль PostgreSQL пользователя `trevor` +- `123456789,987654321` на реальные Telegram ID администраторов + +Сохраните файл: `Ctrl+X`, затем `Y`, затем `Enter` + +### Шаг 4: Запустите скрипт развертывания + +```bash +chmod +x scripts/deploy_server.sh +./scripts/deploy_server.sh +``` + +Скрипт автоматически: +- ✅ Проверит зависимости +- ✅ Создаст виртуальное окружение +- ✅ Установит dependencies из requirements.txt +- ✅ Проверит подключение к БД +- ✅ Запустит миграции +- ✅ Проверит конфигурацию + +### Шаг 5: Запустите бота + +```bash +# Активируем виртуальное окружение +source venv/bin/activate + +# Запускаем бота +python3 main.py +``` + +Если видите "✅ Bot started successfully!", значит всё работает! + +--- + +## 📊 Детальные инструкции + +### 1. Подготовка сервера + +#### 1.1 Установка необходимых пакетов + +```bash +sudo apt-get update +sudo apt-get install -y python3 python3-pip python3-venv postgresql-client git +``` + +#### 1.2 Проверка Python + +```bash +python3 --version +# Должно быть 3.8 или выше +``` + +### 2. Подготовка PostgreSQL + +#### 2.1 Подключитесь к PostgreSQL + +```bash +psql -h localhost -U postgres +``` + +#### 2.2 Создайте пользователя и БД + +```sql +-- Создание пользователя (если еще не существует) +CREATE USER trevor WITH PASSWORD 'your_secure_password'; + +-- Создание базы данных +CREATE DATABASE lottery_bot OWNER trevor; + +-- Даем права +GRANT ALL PRIVILEGES ON DATABASE lottery_bot TO trevor; +GRANT ALL PRIVILEGES ON SCHEMA public TO trevor; + +-- Выход +\q +``` + +#### 2.3 Проверьте подключение + +```bash +psql -h localhost -U trevor -d lottery_bot -c "SELECT 1" +``` + +Должен вернуть результат без ошибок. + +### 3. Клонирование/обновление приложения + +```bash +# Если еще не скачно +git clone <ваш-репозиторий> new_lottery_bot +cd new_lottery_bot + +# Или если уже есть, обновить +cd new_lottery_bot +git pull origin main +``` + +### 4. Конфигурация приложения + +#### 4.1 Создайте .env файл + +```bash +cat > .env << 'EOL' +BOT_TOKEN=your_bot_token +DATABASE_URL=postgresql://trevor:password@localhost:5432/lottery_bot +ADMIN_IDS=123456789 +LOG_LEVEL=INFO +EOL +``` + +#### 4.2 Проверьте содержимое .env + +```bash +cat .env +``` + +### 5. Запуск развертывания + +```bash +# Сделайте скрипт исполняемым +chmod +x scripts/deploy_server.sh + +# Запустите скрипт +./scripts/deploy_server.sh +``` + +### 6. Запуск бота + +#### Вариант 1: Директный запуск (тестирование) + +```bash +source venv/bin/activate +python3 main.py +``` + +#### Вариант 2: Фоновый запуск (screen) + +```bash +source venv/bin/activate +screen -S lottery-bot +python3 main.py +# Нажмите Ctrl+A, затем D для отключения от session +``` + +Для повторного подключения: +```bash +screen -r lottery-bot +``` + +#### Вариант 3: Systemd сервис (рекомендуется для production) + +Создайте файл `/etc/systemd/system/lottery-bot.service`: + +```bash +sudo nano /etc/systemd/system/lottery-bot.service +``` + +Содержимое: + +```ini +[Unit] +Description=Lottery Bot Telegram +After=network.target postgresql.service + +[Service] +Type=simple +User=trevor +WorkingDirectory=/home/trevor/new_lottery_bot +Environment="PATH=/home/trevor/new_lottery_bot/venv/bin" +ExecStart=/home/trevor/new_lottery_bot/venv/bin/python3 main.py +Restart=on-failure +RestartSec=10 +StandardOutput=append:/home/trevor/new_lottery_bot/logs/bot.log +StandardError=append:/home/trevor/new_lottery_bot/logs/bot.log + +[Install] +WantedBy=multi-user.target +``` + +Запустите сервис: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable lottery-bot +sudo systemctl start lottery-bot + +# Проверьте статус +sudo systemctl status lottery-bot + +# Просмотр логов +sudo journalctl -u lottery-bot -f +``` + +--- + +## 🔍 Проверка и диагностика + +### Проверка подключения к БД + +```bash +source venv/bin/activate +python3 << 'EOF' +from src.core.database import async_session_maker +from sqlalchemy import text +import asyncio + +async def test(): + async with async_session_maker() as session: + result = await session.execute(text("SELECT 1")) + print("✅ БД работает!") + +asyncio.run(test()) +EOF +``` + +### Проверка работы бота + +```bash +# Отправьте сообщение боту +# Если бот отвечает - всё работает! +``` + +### Просмотр логов + +```bash +# Разовый запуск с логами +source venv/bin/activate +python3 main.py 2>&1 | tee logs/bot.log + +# На фоне (systemd) +sudo journalctl -u lottery-bot -n 50 -f +``` + +### Устранение проблем + +#### Проблема: "ModuleNotFoundError" + +```bash +source venv/bin/activate +pip3 install -r requirements.txt +``` + +#### Проблема: "Connection refused" (БД) + +```bash +# Проверьте, работает ли PostgreSQL +sudo systemctl status postgresql + +# Проверьте переменную DATABASE_URL в .env +cat .env | grep DATABASE_URL +``` + +#### Проблема: "Bot token is invalid" + +```bash +# Проверьте токен в .env +cat .env | grep BOT_TOKEN + +# Получите новый токен от @BotFather +``` + +--- + +## 📈 Масштабирование и Production + +### Использование Docker (рекомендуется) + +```bash +# Убедитесь, что Docker установлен +docker --version +docker-compose --version + +# Запустите в Docker +docker-compose up -d + +# Просмотр логов +docker-compose logs -f lottery-bot + +# Остановка +docker-compose down +``` + +### Настройка reverse proxy (Nginx) + +Для API или веб-интерфейса (если добавится): + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +### Резервное копирование БД + +```bash +# Ежедневное резервное копирование +0 3 * * * /home/trevor/new_lottery_bot/scripts/backup_db.sh +``` + +--- + +## 🆘 Контакты и помощь + +- **Документация проекта**: `docs/` директория +- **Система управления администраторами**: `docs/ADMIN_MANAGEMENT_SYSTEM.md` +- **Логи приложения**: `logs/` директория + +--- + +## ✅ Чек-лист развертывания + +- [ ] Python 3 установлен +- [ ] PostgreSQL установлен и работает +- [ ] БД `lottery_bot` создана +- [ ] Пользователь `trevor` создан с правами +- [ ] Проект клонирован/обновлен +- [ ] Файл `.env` создан с корректными данными +- [ ] Скрипт `deploy_server.sh` запущен успешно +- [ ] Миграции БД завершены +- [ ] Бот запущен и отвечает на сообщения +- [ ] Логирование работает + +После завершения всех пунктов - приложение готово к использованию! 🎉 diff --git a/migrations/versions/20260218_0402_12_merge_migration.py b/migrations/versions/20260218_0402_12_merge_migration.py new file mode 100644 index 0000000..497f383 --- /dev/null +++ b/migrations/versions/20260218_0402_12_merge_migration.py @@ -0,0 +1,24 @@ +"""merge branches + +Revision ID: merge_migration +Revises: 41aae82e631b, cd31303a681c +Create Date: 2026-02-18 04:02:12.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'merge_migration' +down_revision = ('41aae82e631b', 'cd31303a681c') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass diff --git a/scripts/backup_db.sh b/scripts/backup_db.sh new file mode 100644 index 0000000..4c8c316 --- /dev/null +++ b/scripts/backup_db.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Скрипт резервного копирования БД PostgreSQL +# Использование: ./backup_db.sh +# Для автоматизации добавьте в crontab: 0 3 * * * /path/to/backup_db.sh + +set -e + +# Переменные +BACKUP_DIR="${HOME}/new_lottery_bot/backups" +DB_NAME="${DATABASE_DEFAULT:-lottery_bot}" +DB_USER="${DATABASE_USER:-trevor}" +DB_HOST="${DATABASE_HOST:-localhost}" +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +BACKUP_FILE="${BACKUP_DIR}/lottery_bot_${TIMESTAMP}.sql.gz" +KEEP_DAYS=7 # Хранить резервные копии 7 дней + +# Цвета +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warn() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +echo "🔄 Резервное копирование БД PostgreSQL" +echo "========================================" + +# Создание директории для резервных копий +if [ ! -d "$BACKUP_DIR" ]; then + log_info "Создание директории для резервных копий..." + mkdir -p "$BACKUP_DIR" +fi + +# Получение размера БД перед резервной копией +DB_SIZE=$(psql -h "$DB_HOST" -U "$DB_USER" -t -c " + SELECT pg_size_pretty(pg_database.datsize) + FROM pg_database + WHERE datname = '$DB_NAME'; +") + +log_info "База данных: $DB_NAME" +log_info "Размер БД: $DB_SIZE" +log_info "Файл резервной копии: $BACKUP_FILE" + +# Создание резервной копии +echo "" +echo "⏳ Выполнение резервной копии..." + +if pg_dump -h "$DB_HOST" -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE" 2>/dev/null; then + BACKUP_SIZE=$(ls -lh "$BACKUP_FILE" | awk '{print $5}') + log_info "Резервная копия создана успешно" + log_info "Размер файла: $BACKUP_SIZE" +else + log_error "Ошибка при создании резервной копии" + exit 1 +fi + +# Удаление старых резервных копий +echo "" +echo "🧹 Удаление старых резервных копий..." +find "$BACKUP_DIR" -name "lottery_bot_*.sql.gz" -mtime +$KEEP_DAYS -exec rm -f {} \; +log_info "Очистка завершена (хранятся копии за последние $KEEP_DAYS дней)" + +# Статистика +echo "" +echo "📊 Статистика резервных копий:" +TOTAL_SIZE=$(du -sh "$BACKUP_DIR" | awk '{print $1}') +COUNT=$(ls -1 "$BACKUP_DIR"/lottery_bot_*.sql.gz 2>/dev/null | wc -l) +log_info "Всего резервных копий: $COUNT" +log_info "Общий размер: $TOTAL_SIZE" + +# Информация о последних копиях +echo "" +echo "📋 Последние 5 резервных копий:" +ls -1t "$BACKUP_DIR"/lottery_bot_*.sql.gz 2>/dev/null | head -5 | while read file; do + size=$(ls -lh "$file" | awk '{print $5}') + name=$(basename "$file") + echo " • $name ($size)" +done + +echo "" +echo "========================================" +log_info "Резервная копия завершена!" + +# Дополнительная информация +echo "" +echo "💡 Советы:" +echo " • Важные копии загружайте на облако" +echo " • Тестируйте восстановление из копий" +echo " • Добавьте в crontab для автоматизации:" +echo " 0 3 * * * $PWD/scripts/backup_db.sh" +echo "" diff --git a/scripts/check_db.py b/scripts/check_db.py new file mode 100644 index 0000000..389eda8 --- /dev/null +++ b/scripts/check_db.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +""" +Скрипт для проверки и инициализации БД перед запуском бота +""" + +import asyncio +import sys +from pathlib import Path + +# Добавляем путь к проекту +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.core.database import engine, async_session_maker, Base +from src.core.models import User, Lottery, Participation, Winner, Account +from sqlalchemy import text, inspect, select +import logging + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + + +async def check_db_connection(): + """Проверка подключения к БД""" + logger.info("🔍 Проверка подключения к БД...") + try: + async with async_session_maker() as session: + result = await session.execute(text("SELECT 1")) + logger.info("✅ Подключение к БД успешно") + return True + except Exception as e: + logger.error(f"❌ Ошибка подключения к БД: {e}") + return False + + +async def check_tables(): + """Проверка наличия таблиц""" + logger.info("📊 Проверка таблиц БД...") + + async with engine.begin() as conn: + inspector = inspect(conn) + tables = inspector.get_table_names() + + required_tables = ['users', 'lotteries', 'participations', 'winners', 'accounts'] + + missing_tables = [t for t in required_tables if t not in tables] + + if missing_tables: + logger.warning(f"⚠️ Отсутствуют таблицы: {', '.join(missing_tables)}") + return False, missing_tables + else: + logger.info(f"✅ Все необходимые таблицы найдены: {', '.join(required_tables)}") + return True, [] + + +async def create_tables(): + """Создание таблиц БД""" + logger.info("📝 Создание таблиц БД...") + try: + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + logger.info("✅ Таблицы созданы успешно") + return True + except Exception as e: + logger.error(f"❌ Ошибка при создании таблиц: {e}") + return False + + +async def check_data(): + """Проверка наличия данных""" + logger.info("📈 Проверка данных в БД...") + + async with async_session_maker() as session: + users_count = await session.execute(select(User)) + users_count = len(users_count.scalars().all()) + + lotteries_count = await session.execute(select(Lottery)) + lotteries_count = len(lotteries_count.scalars().all()) + + logger.info(f"👥 Пользователей: {users_count}") + logger.info(f"🎲 Розыгрышей: {lotteries_count}") + + return { + 'users': users_count, + 'lotteries': lotteries_count + } + + +async def main(): + """Главная функция""" + logger.info("=" * 60) + logger.info("🔧 Проверка и инициализация БД") + logger.info("=" * 60) + + # Шаг 1: Проверка подключения + if not await check_db_connection(): + logger.error("❌ Не удалось подключиться к БД. Проверьте переменную DATABASE_URL") + return False + + # Шаг 2: Проверка таблиц + tables_exist, missing_tables = await check_tables() + + if not tables_exist: + logger.info("🔄 Создание отсутствующих таблиц...") + if not await create_tables(): + logger.error("❌ Не удалось создать таблицы") + return False + logger.info("✅ Таблицы созданы") + + # Шаг 3: Проверка данных + data = await check_data() + + # Итоговая информация + logger.info("") + logger.info("=" * 60) + logger.info("✅ БД готова к работе!") + logger.info("=" * 60) + logger.info("") + logger.info("📋 Информация о БД:") + logger.info(f" 👥 Пользователей: {data['users']}") + logger.info(f" 🎲 Розыгрышей: {data['lotteries']}") + logger.info("") + logger.info("🚀 Вы можете запустить бота командой:") + logger.info(" python3 main.py") + logger.info("") + + return True + + +if __name__ == "__main__": + success = asyncio.run(main()) + sys.exit(0 if success else 1) diff --git a/scripts/deploy_and_run.sh b/scripts/deploy_and_run.sh new file mode 100644 index 0000000..d501636 --- /dev/null +++ b/scripts/deploy_and_run.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Быстрый запуск: deploy_and_run.sh +# Выполняет развертывание и запуск бота одной командой + +set -e + +echo "🚀 Lottery Bot - Быстрое развертывание и запуск" +echo "==================================================" +echo "" + +# Проверка .env +if [ ! -f ".env" ]; then + echo "❌ Файл .env не найден!" + echo "" + echo "Создайте .env файл с содержимым:" + echo "────────────────────────────────────────────" + cat << 'EOF' +BOT_TOKEN=your_bot_token +DATABASE_URL=postgresql://trevor:password@localhost:5432/lottery_bot +ADMIN_IDS=123456789 +LOG_LEVEL=INFO +EOF + echo "────────────────────────────────────────────" + echo "" + exit 1 +fi + +echo "✅ Файл .env найден" + +# Создание виртуального окружения +if [ ! -d "venv" ]; then + echo "📦 Создание виртуального окружения..." + python3 -m venv venv +fi + +# Активация +source venv/bin/activate + +# Установка dependencies +echo "📚 Установка dependencies..." +pip3 install -q --upgrade pip +pip3 install -q -r requirements.txt + +# Проверка БД +echo "🗄️ Проверка и инициализация БД..." +python3 scripts/check_db.py + +# Запуск бота +echo "" +echo "🤖 Запуск бота..." +echo "==================================================" +echo "" +python3 main.py diff --git a/scripts/deploy_server.sh b/scripts/deploy_server.sh new file mode 100644 index 0000000..d8ef976 --- /dev/null +++ b/scripts/deploy_server.sh @@ -0,0 +1,172 @@ +#!/bin/bash +# Скрипт развертывания lottery_bot на сервере +# Использование: ./deploy_server.sh + +set -e + +echo "🔧 ============================================" +echo "🔧 Развертывание Lottery Bot на сервер" +echo "🔧 ============================================" +echo "" + +# Цвета для вывода +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Функция для вывода сообщений +log_info() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warn() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Проверка переменных окружения +if [ -z "$DATABASE_URL" ]; then + log_error "DATABASE_URL не установлен в .env" + echo "Пример: export DATABASE_URL='postgresql://user:password@host:5432/lottery_bot'" + exit 1 +fi + +if [ -z "$BOT_TOKEN" ]; then + log_error "BOT_TOKEN не установлен в .env" + exit 1 +fi + +log_info "Переменные окружения проверены" + +# 1. Проверка зависимостей +echo "" +echo "📦 Проверка зависимостей..." + +if ! command -v python3 &> /dev/null; then + log_error "Python 3 не установлен" + exit 1 +fi +log_info "Python 3 найден: $(python3 --version)" + +if ! command -v pip3 &> /dev/null; then + log_error "pip3 не установлен" + exit 1 +fi +log_info "pip3 установлен" + +# 2. Создание виртуального окружения +echo "" +echo "🐍 Подготовка виртуального окружения..." + +if [ ! -d "venv" ]; then + log_info "Создание виртуального окружения..." + python3 -m venv venv +else + log_warn "Виртуальное окружение уже существует" +fi + +# Активируем виртуальное окружение +source venv/bin/activate +log_info "Виртуальное окружение активировано" + +# 3. Установка зависимостей +echo "" +echo "📚 Установка зависимостей из requirements.txt..." + +if [ -f "requirements.txt" ]; then + pip3 install --upgrade pip setuptools wheel -q + pip3 install -r requirements.txt -q + log_info "Зависимости установлены" +else + log_error "requirements.txt не найден" + exit 1 +fi + +# 4. Проверка подключения к БД +echo "" +echo "🗄️ Проверка подключения к базе данных..." + +python3 << 'EOF' +import asyncio +from src.core.database import async_session_maker +from sqlalchemy import text + +async def test_db(): + try: + async with async_session_maker() as session: + result = await session.execute(text("SELECT 1")) + print("✅ Подключение к БД успешно") + return True + except Exception as e: + print(f"❌ Ошибка подключения: {e}") + return False + +if not asyncio.run(test_db()): + exit(1) +EOF + +if [ $? -ne 0 ]; then + log_error "Не удалось подключиться к базе данных" + exit 1 +fi + +# 5. Запуск миграций +echo "" +echo "📝 Запуск миграций базы данных..." + +if command -v alembic &> /dev/null; then + log_info "Alembic найден, запуск миграций..." + alembic upgrade head + log_info "Миграции завершены" +else + log_warn "Alembic не найден, пропуск миграций Alembic" + + # Используем встроенный скрипт инициализации + if [ -f "scripts/db_setup.py" ]; then + log_info "Использование скрипта инициализации БД..." + python3 scripts/db_setup.py + log_info "БД инициализирована" + fi +fi + +# 6. Проверка конфигурации +echo "" +echo "⚙️ Проверка конфигурации..." + +python3 << 'EOF' +from src.core.config import BOT_TOKEN, DATABASE_URL, ADMIN_IDS + +print(f"✅ BOT_TOKEN загружен") +print(f"✅ DATABASE_URL: {DATABASE_URL[:50]}...") +print(f"✅ ADMIN_IDS: {ADMIN_IDS if ADMIN_IDS else 'Не установлены'}") +EOF + +log_info "Конфигурация проверена" + +# 7. Информация о запуске +echo "" +echo "🚀 ============================================" +echo "🚀 Приложение готово к запуску" +echo "🚀 ============================================" +echo "" +echo "📋 Команды для запуска:" +echo "" +echo "Режим разработки:" +echo " python3 main.py" +echo "" +echo "Производство (с systemd):" +echo " sudo systemctl start lottery-bot" +echo " sudo systemctl enable lottery-bot" +echo "" +echo "Docker:" +echo " docker-compose up -d" +echo "" +echo "⚙️ Переменные окружения:" +echo " DATABASE_URL: $(echo $DATABASE_URL | cut -c1-50)..." +echo " BOT_TOKEN: $(echo $BOT_TOKEN | cut -c1-20)...${BOT_TOKEN: -5}" +echo "" +log_info "Развертывание завершено!" diff --git a/scripts/manage_admins.py b/scripts/manage_admins.py new file mode 100644 index 0000000..eab4f25 --- /dev/null +++ b/scripts/manage_admins.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +""" +Скрипт для управления администраторами через CLI +Используется для быстрого доступа к функциям управления админами +""" + +import asyncio +import sys +from pathlib import Path + +# Добавляем путь к проекту +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.core.database import async_session_maker +from src.core.services import UserService +from src.core.config import ADMIN_IDS + + +async def list_admins(): + """Показать список всех администраторов""" + async with async_session_maker() as session: + from sqlalchemy import select + from src.core.models import User + + # Получаем всех администраторов из БД + result = await session.execute( + select(User).where(User.is_admin == True).order_by(User.created_at.desc()) + ) + db_admins = result.scalars().all() + + print("\n" + "="*60) + print("👑 СПИСОК АДМИНИСТРАТОРОВ") + print("="*60) + + print("\n🔴 Главные администраторы (.env):") + if ADMIN_IDS: + for admin_id in ADMIN_IDS: + print(f" • ID: {admin_id}") + else: + print(" Нет главных администраторов") + + print("\n🟠 Назначенные администраторы:") + if db_admins: + for admin in db_admins: + name = admin.first_name or admin.username or f"@ID_{admin.telegram_id}" + print(f" • {name} (Telegram ID: {admin.telegram_id})") + else: + print(" Нет назначенных администраторов") + + print("\n" + "="*60 + "\n") + + +async def add_admin(telegram_id: int): + """Добавить администратора""" + async with async_session_maker() as session: + # Проверяем, существует ли пользователь + user = await UserService.get_user_by_telegram_id(session, telegram_id) + + if not user: + print(f"❌ Пользователь с ID {telegram_id} не найден") + return + + if telegram_id in ADMIN_IDS: + print(f"❌ ID {telegram_id} - это главный администратор (.env)") + return + + if user.is_admin: + print(f"❌ Пользователь {user.first_name or user.username} уже администратор") + return + + # Назначаем админа + success = await UserService.set_admin(session, telegram_id, is_admin=True) + + if success: + name = user.first_name or user.username or f"@ID_{telegram_id}" + print(f"✅ {name} назначен администратором") + else: + print(f"❌ Ошибка при назначении администратора") + + +async def remove_admin(telegram_id: int): + """Удалить администратора""" + async with async_session_maker() as session: + if telegram_id in ADMIN_IDS: + print(f"❌ Нельзя удалить главного администратора (.env)") + print(f" Для изменения отредактируйте .env") + return + + # Проверяем, существует ли пользователь + user = await UserService.get_user_by_telegram_id(session, telegram_id) + + if not user: + print(f"❌ Пользователь с ID {telegram_id} не найден") + return + + if not user.is_admin: + print(f"❌ Пользователь {user.first_name or user.username} не является администратором") + return + + # Удаляем админа + success = await UserService.set_admin(session, telegram_id, is_admin=False) + + if success: + name = user.first_name or user.username or f"@ID_{telegram_id}" + print(f"✅ Права администратора удалены у {name}") + else: + print(f"❌ Ошибка при удалении прав администратора") + + +async def main(): + """Главная функция""" + if len(sys.argv) < 2: + print(""" +Использование: python scripts/manage_admins.py <команда> [аргументы] + +Команды: + list - Показать список всех администраторов + add - Добавить администратора (по Telegram ID) + remove - Удалить администратора (по Telegram ID) + +Примеры: + python scripts/manage_admins.py list + python scripts/manage_admins.py add 123456789 + python scripts/manage_admins.py remove 123456789 + """) + return + + command = sys.argv[1].lower() + + if command == "list": + await list_admins() + + elif command == "add": + if len(sys.argv) < 3: + print("❌ Требуется указать Telegram ID") + return + try: + telegram_id = int(sys.argv[2]) + await add_admin(telegram_id) + except ValueError: + print("❌ Telegram ID должен быть числом") + + elif command == "remove": + if len(sys.argv) < 3: + print("❌ Требуется указать Telegram ID") + return + try: + telegram_id = int(sys.argv[2]) + await remove_admin(telegram_id) + except ValueError: + print("❌ Telegram ID должен быть числом") + + else: + print(f"❌ Неизвестная команда: {command}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/handlers/admin_panel.py b/src/handlers/admin_panel.py index 6b579ff..cd3e7e5 100644 --- a/src/handlers/admin_panel.py +++ b/src/handlers/admin_panel.py @@ -96,6 +96,13 @@ class AdminStates(StatesGroup): # Управление пользователями user_management_search = State() # Поиск пользователей user_management_view = State() # Просмотр пользователя + + # Управление админами + admin_management_action = State() # Выбор действия (добавить/удалить) + admin_add_search = State() # Поиск пользователя для назначения админом + admin_add_confirm = State() # Подтверждение назначения + admin_remove_select = State() # Выбор админа для удаления + admin_remove_confirm = State() # Подтверждение удаления admin_router = Router() @@ -106,6 +113,11 @@ def is_admin(user_id: int) -> bool: return user_id in ADMIN_IDS +def is_super_admin(user_id: int) -> bool: + """Проверка, является ли пользователь главным администратором (из ADMIN_IDS)""" + return user_id in ADMIN_IDS + + def get_admin_main_keyboard() -> InlineKeyboardMarkup: """Главная админ-панель""" buttons = [ @@ -3079,14 +3091,20 @@ async def show_admin_settings(callback: CallbackQuery): text += "Доступные действия:" - buttons = [ + buttons = [] + + # Кнопка управления админами - только для главных админов + if is_super_admin(callback.from_user.id): + buttons.append([InlineKeyboardButton(text="👑 Управление админами", callback_data="admin_manage_admins")]) + + buttons.extend([ [InlineKeyboardButton(text="💿 Экспорт пользователей", callback_data="admin_export_users")], [InlineKeyboardButton(text="⬆️ Импорт пользователей", callback_data="admin_import_users")], [InlineKeyboardButton(text="💿 Экспорт данных", callback_data="admin_export_data")], [InlineKeyboardButton(text="🧹 Очистка старых данных", callback_data="admin_cleanup")], [InlineKeyboardButton(text="📜 Системная информация", callback_data="admin_system_info")], [InlineKeyboardButton(text="◀️ Назад", callback_data="admin_panel")] - ] + ]) await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons)) @@ -5216,5 +5234,259 @@ async def admin_user_unban(callback: CallbackQuery, state: FSMContext): await callback.answer("❌ Ошибка разблокировки", show_alert=True) +# ========================= +# УПРАВЛЕНИЕ АДМИНИСТРАТОРАМИ +# ========================= + +@admin_router.callback_query(F.data == "admin_manage_admins") +async def manage_admins_menu(callback: CallbackQuery): + """Главное меню управления администраторами""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Только главные администраторы могут управлять правами", show_alert=True) + return + + await callback.answer() + + text = "👑 Управление администраторами\n\n" + text += f"Главные администраторы (.env): {len(ADMIN_IDS)}\n\n" + text += "Выберите действие:" + + buttons = [ + [InlineKeyboardButton(text="➕ Назначить админа", callback_data="admin_add_admin")], + [InlineKeyboardButton(text="➖ Удалить админа", callback_data="admin_remove_admin")], + [InlineKeyboardButton(text="📋 Список админов", callback_data="admin_list_admins_view")], + [InlineKeyboardButton(text="◀️ Назад", callback_data="admin_settings")] + ] + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons), + parse_mode="HTML" + ) + + +@admin_router.callback_query(F.data == "admin_list_admins_view") +async def list_admins_view(callback: CallbackQuery): + """Показать список всех администраторов""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Доступ запрещен", show_alert=True) + return + + await callback.answer() + + async with async_session_maker() as session: + from sqlalchemy import select + + # Получаем всех администраторов (назначенных через БД) + result = await session.execute( + select(User).where(User.is_admin == True).order_by(User.created_at.desc()) + ) + db_admins = result.scalars().all() + + text = "👑 Список администраторов\n\n" + + # Главные администраторы из .env + text += "Главные администраторы (из .env):\n" + for admin_id in ADMIN_IDS: + text += f"🔴 ID: {admin_id}\n" + + text += "\n" + + # Назначенные администраторы + if db_admins: + text += "Назначенные администраторы:\n" + for admin in db_admins: + icon = "🟠" # Назначенный админ + name = admin.first_name or admin.username or f"@ID_{admin.telegram_id}" + text += f"{icon} {name} (ID: {admin.telegram_id})\n" + else: + text += "Назначенные администраторы: нет\n" + + buttons = [ + [InlineKeyboardButton(text="◀️ Назад", callback_data="admin_manage_admins")] + ] + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons), + parse_mode="HTML" + ) + + +@admin_router.callback_query(F.data == "admin_add_admin") +async def add_admin_start(callback: CallbackQuery, state: FSMContext): + """Начать добавление нового администратора""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Доступ запрещен", show_alert=True) + return + + await callback.answer() + + text = "👤 Назначение администратора\n\n" + text += "Введите Telegram ID пользователя или его имя для поиска:" + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="❌ Отмена", callback_data="admin_manage_admins")] + ]), + parse_mode="HTML" + ) + + await state.set_state(AdminStates.admin_add_search) + + +@admin_router.message(StateFilter(AdminStates.admin_add_search)) +async def search_user_for_admin(message: Message, state: FSMContext): + """Поиск пользователя для назначения админом""" + if not is_super_admin(message.from_user.id): + await message.answer("❌ Доступ запрещен") + return + + search_query = message.text.strip() + + async with async_session_maker() as session: + user = None + + # Пробуем найти по ID + try: + telegram_id = int(search_query) + user = await UserService.get_user_by_telegram_id(session, telegram_id) + except ValueError: + # Если не число, ищем по имени или username + users = await UserService.search_users(session, search_query, limit=5) + if users: + user = users[0] + + if not user: + await message.answer("❌ Пользователь не найден") + await state.set_state(AdminStates.admin_add_search) + return + + # Проверяем, не главный ли админ из .env + if user.telegram_id in ADMIN_IDS: + await message.answer("❌ Это главный администратор (.env). Уже имеет максимальные права") + await state.set_state(AdminStates.admin_add_search) + return + + # Проверяем, не админ ли уже + if user.is_admin: + await message.answer("❌ Этот пользователь уже администратор") + await state.set_state(AdminStates.admin_add_search) + return + + # Сохраняем в state и просим подтверждение + await state.update_data(admin_user_id=user.id, admin_telegram_id=user.telegram_id) + + text = "👤 Подтверждение назначения администратора\n\n" + text += f"Имя: {user.first_name or 'не указано'}\n" + text += f"Username: {user.username or 'нет'}\n" + text += f"Telegram ID: {user.telegram_id}\n" + text += f"Зарегистрирован: {user.created_at.strftime('%d.%m.%Y %H:%M') if user.created_at else 'нет'}\n\n" + text += "Вы уверены, что хотите дать этому пользователю права администратора?" + + await message.answer( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="✅ Да, назначить", callback_data="admin_add_confirm_yes"), + InlineKeyboardButton(text="❌ Отмена", callback_data="admin_manage_admins")], + ]), + parse_mode="HTML" + ) + + await state.set_state(AdminStates.admin_add_confirm) + + +@admin_router.callback_query(F.data == "admin_add_confirm_yes") +async def confirm_add_admin(callback: CallbackQuery, state: FSMContext): + """Подтвердить назначение админа""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Доступ запрещен", show_alert=True) + return + + data = await state.get_data() + admin_telegram_id = data.get('admin_telegram_id') + + async with async_session_maker() as session: + success = await UserService.set_admin(session, admin_telegram_id, is_admin=True) + + if success: + await callback.answer("✅ Администратор успешно назначен", show_alert=True) + await state.clear() + await manage_admins_menu(callback) + else: + await callback.answer("❌ Ошибка при назначении администратора", show_alert=True) + + +@admin_router.callback_query(F.data == "admin_remove_admin") +async def remove_admin_start(callback: CallbackQuery, state: FSMContext): + """Начать удаление администратора""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Доступ запрещен", show_alert=True) + return + + await callback.answer() + + async with async_session_maker() as session: + from sqlalchemy import select + + # Получаем всех назначенных администраторов + result = await session.execute( + select(User).where(User.is_admin == True).order_by(User.created_at.desc()) + ) + admins = result.scalars().all() + + if not admins: + await callback.answer("❌ Нет назначенных администраторов", show_alert=True) + return + + text = "🗑️ Выберите администратора для удаления\n\n" + + buttons = [] + for admin in admins[:20]: # Максимум 20 администраторов на странице + name = admin.first_name or admin.username or f"@ID_{admin.telegram_id}" + buttons.append([InlineKeyboardButton( + text=f"🟠 {name}", + callback_data=f"admin_remove_select:{admin.telegram_id}" + )]) + + buttons.append([InlineKeyboardButton(text="❌ Отмена", callback_data="admin_manage_admins")]) + + await callback.message.edit_text( + text, + reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons), + parse_mode="HTML" + ) + + await state.set_state(AdminStates.admin_remove_select) + + +@admin_router.callback_query(F.data.startswith("admin_remove_select:")) +async def confirm_remove_admin(callback: CallbackQuery, state: FSMContext): + """Подтвердить удаление администратора""" + if not is_super_admin(callback.from_user.id): + await callback.answer("❌ Доступ запрещен", show_alert=True) + return + + admin_telegram_id = int(callback.data.split(":")[1]) + + async with async_session_maker() as session: + user = await UserService.get_user_by_telegram_id(session, admin_telegram_id) + + if not user: + await callback.answer("❌ Пользователь не найден", show_alert=True) + return + + # Снять права администратора + success = await UserService.set_admin(session, admin_telegram_id, is_admin=False) + + if success: + await callback.answer("✅ Права администратора удалены", show_alert=True) + await state.clear() + await manage_admins_menu(callback) + else: + await callback.answer("❌ Ошибка при удалении прав", show_alert=True) + + # Экспорт роутера __all__ = ['admin_router'] \ No newline at end of file