Compare commits
2 Commits
4160d69fa7
...
2db39b0652
| Author | SHA1 | Date | |
|---|---|---|---|
| 2db39b0652 | |||
| e1b4465f89 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,8 @@
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
.env.prod
|
||||||
|
|
||||||
|
|
||||||
# База данных
|
# База данных
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
202
DEPLOY_QUICK_START.md
Normal file
202
DEPLOY_QUICK_START.md
Normal file
@@ -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/` 📚
|
||||||
149
docs/ADMIN_MANAGEMENT_CHANGELOG.md
Normal file
149
docs/ADMIN_MANAGEMENT_CHANGELOG.md
Normal file
@@ -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` уже существует)
|
||||||
173
docs/ADMIN_MANAGEMENT_SYSTEM.md
Normal file
173
docs/ADMIN_MANAGEMENT_SYSTEM.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
Обе функции используются для управления доступом к различным функциям админ-панели.
|
||||||
374
docs/SERVER_DEPLOYMENT.md
Normal file
374
docs/SERVER_DEPLOYMENT.md
Normal file
@@ -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` запущен успешно
|
||||||
|
- [ ] Миграции БД завершены
|
||||||
|
- [ ] Бот запущен и отвечает на сообщения
|
||||||
|
- [ ] Логирование работает
|
||||||
|
|
||||||
|
После завершения всех пунктов - приложение готово к использованию! 🎉
|
||||||
24
migrations/versions/20260218_0402_12_merge_migration.py
Normal file
24
migrations/versions/20260218_0402_12_merge_migration.py
Normal file
@@ -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
|
||||||
102
scripts/backup_db.sh
Normal file
102
scripts/backup_db.sh
Normal file
@@ -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 ""
|
||||||
134
scripts/check_db.py
Normal file
134
scripts/check_db.py
Normal file
@@ -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)
|
||||||
53
scripts/deploy_and_run.sh
Normal file
53
scripts/deploy_and_run.sh
Normal file
@@ -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
|
||||||
172
scripts/deploy_server.sh
Normal file
172
scripts/deploy_server.sh
Normal file
@@ -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 "Развертывание завершено!"
|
||||||
158
scripts/manage_admins.py
Normal file
158
scripts/manage_admins.py
Normal file
@@ -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 <id> - Добавить администратора (по Telegram ID)
|
||||||
|
remove <id> - Удалить администратора (по 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())
|
||||||
@@ -101,6 +101,13 @@ class AdminStates(StatesGroup):
|
|||||||
user_management_search = State() # Поиск пользователей
|
user_management_search = State() # Поиск пользователей
|
||||||
user_management_view = 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()
|
admin_router = Router()
|
||||||
|
|
||||||
@@ -110,6 +117,11 @@ def is_admin(user_id: int) -> bool:
|
|||||||
return user_id in ADMIN_IDS
|
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:
|
def get_admin_main_keyboard() -> InlineKeyboardMarkup:
|
||||||
"""Главная админ-панель"""
|
"""Главная админ-панель"""
|
||||||
buttons = [
|
buttons = [
|
||||||
@@ -3657,13 +3669,19 @@ async def show_admin_settings(callback: CallbackQuery):
|
|||||||
|
|
||||||
text += "Доступные действия:"
|
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_export_users")],
|
||||||
[InlineKeyboardButton(text="⬆️ Импорт пользователей", callback_data="admin_import_users")],
|
[InlineKeyboardButton(text="⬆️ Импорт пользователей", callback_data="admin_import_users")],
|
||||||
[InlineKeyboardButton(text="🧹 Очистка старых данных", callback_data="admin_cleanup")],
|
[InlineKeyboardButton(text="🧹 Очистка старых данных", callback_data="admin_cleanup")],
|
||||||
[InlineKeyboardButton(text="📜 Системная информация", callback_data="admin_system_info")],
|
[InlineKeyboardButton(text="📜 Системная информация", callback_data="admin_system_info")],
|
||||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="admin_panel")]
|
[InlineKeyboardButton(text="◀️ Назад", callback_data="admin_panel")]
|
||||||
]
|
])
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||||
|
|
||||||
@@ -5793,5 +5811,259 @@ async def admin_user_unban(callback: CallbackQuery, state: FSMContext):
|
|||||||
await callback.answer("❌ Ошибка разблокировки", show_alert=True)
|
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 = "👑 <b>Управление администраторами</b>\n\n"
|
||||||
|
text += f"Главные администраторы (.env): <code>{len(ADMIN_IDS)}</code>\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 = "👑 <b>Список администраторов</b>\n\n"
|
||||||
|
|
||||||
|
# Главные администраторы из .env
|
||||||
|
text += "<b>Главные администраторы (из .env):</b>\n"
|
||||||
|
for admin_id in ADMIN_IDS:
|
||||||
|
text += f"🔴 ID: <code>{admin_id}</code>\n"
|
||||||
|
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
|
# Назначенные администраторы
|
||||||
|
if db_admins:
|
||||||
|
text += "<b>Назначенные администраторы:</b>\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: <code>{admin.telegram_id}</code>)\n"
|
||||||
|
else:
|
||||||
|
text += "<b>Назначенные администраторы:</b> нет\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 = "👤 <b>Назначение администратора</b>\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 = "👤 <b>Подтверждение назначения администратора</b>\n\n"
|
||||||
|
text += f"Имя: {user.first_name or 'не указано'}\n"
|
||||||
|
text += f"Username: {user.username or 'нет'}\n"
|
||||||
|
text += f"Telegram ID: <code>{user.telegram_id}</code>\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 = "🗑️ <b>Выберите администратора для удаления</b>\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']
|
__all__ = ['admin_router']
|
||||||
Reference in New Issue
Block a user