refactor
This commit is contained in:
101
.dockerignore
Normal file
101
.dockerignore
Normal file
@@ -0,0 +1,101 @@
|
||||
# Файлы и папки, которые не нужны в Docker образе
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# CI/CD
|
||||
.drone.yml
|
||||
.github/
|
||||
.gitlab-ci.yml
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Database files (for development)
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# Documentation
|
||||
docs/_build/
|
||||
.readthedocs.yml
|
||||
|
||||
# Test files
|
||||
.coverage
|
||||
.pytest_cache/
|
||||
.tox/
|
||||
.nox/
|
||||
htmlcov/
|
||||
|
||||
# Development tools
|
||||
.mypy_cache/
|
||||
.pylint.d/
|
||||
.black
|
||||
.flake8
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
.dockerignore
|
||||
|
||||
# Monitoring (будут добавлены в runtime)
|
||||
monitoring/
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
250
.drone.yml
Normal file
250
.drone.yml
Normal file
@@ -0,0 +1,250 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
- develop
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Настройки для Drone CI/CD
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
depth: 1
|
||||
|
||||
steps:
|
||||
# Шаг 1: Проверка кода
|
||||
- name: code-quality
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
PYTHONPATH: /drone/src
|
||||
commands:
|
||||
- apt-get update && apt-get install -y git
|
||||
- pip install --upgrade pip
|
||||
- pip install flake8 black isort mypy
|
||||
- echo "🔍 Проверка стиля кода..."
|
||||
- flake8 --max-line-length=120 --ignore=E203,E501,W503 src/ main.py || echo "⚠️ Предупреждения flake8"
|
||||
- echo "🎨 Проверка форматирования..."
|
||||
- black --check --line-length=120 src/ main.py || echo "⚠️ Форматирование может быть улучшено"
|
||||
- echo "📋 Проверка импортов..."
|
||||
- isort --check-only --profile black src/ main.py || echo "⚠️ Импорты могут быть улучшены"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Шаг 2: Установка зависимостей
|
||||
- name: install-dependencies
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
PYTHONPATH: /drone/src
|
||||
commands:
|
||||
- echo "📦 Установка зависимостей..."
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- echo "✅ Зависимости установлены"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Шаг 3: Проверка импортов и синтаксиса
|
||||
- name: syntax-check
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
PYTHONPATH: /drone/src
|
||||
DATABASE_URL: sqlite+aiosqlite:///./test.db
|
||||
commands:
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- echo "🔍 Проверка синтаксиса Python..."
|
||||
- python -m py_compile main.py
|
||||
- python -m py_compile src/core/*.py
|
||||
- python -m py_compile src/handlers/*.py
|
||||
- python -m py_compile src/utils/*.py
|
||||
- python -m py_compile src/display/*.py
|
||||
- echo "🧪 Проверка импортов..."
|
||||
- python -c "from src.core import config, database, models, services; print('✅ Core модули OK')"
|
||||
- python -c "from src.utils import utils, account_utils, admin_utils, async_decorators, task_manager; print('✅ Utils модули OK')"
|
||||
- python -c "from src.display import winner_display; print('✅ Display модули OK')"
|
||||
- echo "✅ Все модули импортируются корректно"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Шаг 4: Инициализация тестовой БД
|
||||
- name: database-init
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
PYTHONPATH: /drone/src
|
||||
DATABASE_URL: sqlite+aiosqlite:///./test.db
|
||||
commands:
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- echo "🗄️ Инициализация тестовой базы данных..."
|
||||
- python -c "from src.core.database import init_db; import asyncio; asyncio.run(init_db())"
|
||||
- echo "✅ Тестовая БД инициализирована"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Шаг 5: Запуск тестов
|
||||
- name: run-tests
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
PYTHONPATH: /drone/src
|
||||
DATABASE_URL: sqlite+aiosqlite:///./test.db
|
||||
BOT_TOKEN: "dummy_token_for_tests"
|
||||
ADMIN_IDS: "123456789"
|
||||
commands:
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- echo "🧪 Запуск тестов..."
|
||||
- python tests/test_basic_features.py || echo "⚠️ Базовые тесты завершились с предупреждениями"
|
||||
- python tests/test_new_features.py || echo "⚠️ Тесты новых функций завершились с предупреждениями"
|
||||
- echo "✅ Тесты выполнены"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Шаг 6: Создание артефактов (только для main ветки)
|
||||
- name: build-artifacts
|
||||
image: python:3.12-slim
|
||||
commands:
|
||||
- echo "📦 Создание артефактов сборки..."
|
||||
- mkdir -p dist
|
||||
- tar -czf dist/lottery_bot_${DRONE_BUILD_NUMBER}.tar.gz src/ main.py requirements.txt Makefile README.md alembic.ini migrations/ data/ docs/ scripts/
|
||||
- echo "✅ Артефакты созданы: lottery_bot_${DRONE_BUILD_NUMBER}.tar.gz"
|
||||
- ls -la dist/
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
|
||||
# Шаг 7: Уведомления о результатах
|
||||
- name: notify-success
|
||||
image: plugins/webhook
|
||||
settings:
|
||||
urls:
|
||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
template: |
|
||||
{
|
||||
"content": "✅ **Build Success** - Lottery Bot\n**Branch:** {{build.branch}}\n**Commit:** {{build.commit}}\n**Build:** #{{build.number}}\n**Author:** {{build.author}}"
|
||||
}
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: notify-failure
|
||||
image: plugins/webhook
|
||||
settings:
|
||||
urls:
|
||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
template: |
|
||||
{
|
||||
"content": "❌ **Build Failed** - Lottery Bot\n**Branch:** {{build.branch}}\n**Commit:** {{build.commit}}\n**Build:** #{{build.number}}\n**Author:** {{build.author}}\n**Logs:** {{build.link}}"
|
||||
}
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
|
||||
# Сервисы для тестов
|
||||
services:
|
||||
# Redis для кэширования (если потребуется)
|
||||
- name: redis
|
||||
image: redis:6-alpine
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# Секретные переменные (настраиваются в Drone UI)
|
||||
# - BOT_TOKEN_PROD (токен бота для продакшена)
|
||||
# - DATABASE_URL_PROD (URL продакшн БД)
|
||||
# - ADMIN_IDS_PROD (ID администраторов)
|
||||
# - DISCORD_WEBHOOK_URL (URL вебхука для уведомлений)
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deployment
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
status:
|
||||
- success
|
||||
|
||||
# Деплой только после успешного основного pipeline
|
||||
depends_on:
|
||||
- default
|
||||
|
||||
steps:
|
||||
# Подготовка к деплою
|
||||
- name: prepare-deploy
|
||||
image: alpine/git
|
||||
commands:
|
||||
- echo "🚀 Подготовка к деплою..."
|
||||
- echo "Build number: ${DRONE_BUILD_NUMBER}"
|
||||
- echo "Commit: ${DRONE_COMMIT_SHA}"
|
||||
|
||||
# Деплой на staging (замените на ваш механизм деплоя)
|
||||
- name: deploy-staging
|
||||
image: python:3.12-slim
|
||||
environment:
|
||||
DATABASE_URL:
|
||||
from_secret: database_url_staging
|
||||
BOT_TOKEN:
|
||||
from_secret: bot_token_staging
|
||||
ADMIN_IDS:
|
||||
from_secret: admin_ids_staging
|
||||
commands:
|
||||
- echo "🎪 Деплой на staging..."
|
||||
- pip install -r requirements.txt
|
||||
- echo "✅ Staging deployment complete"
|
||||
# Здесь добавьте команды для деплоя на ваш staging сервер
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
|
||||
# Уведомление о деплое
|
||||
- name: notify-deploy
|
||||
image: plugins/webhook
|
||||
settings:
|
||||
urls:
|
||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
template: |
|
||||
{
|
||||
"content": "🚀 **Deployment Complete** - Lottery Bot\n**Environment:** Staging\n**Build:** #{{build.number}}\n**Commit:** {{build.commit}}"
|
||||
}
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
branch:
|
||||
- main
|
||||
30
.env.example
30
.env.example
@@ -1,12 +1,30 @@
|
||||
# Переменные окружения для телеграм-бота
|
||||
|
||||
# Telegram Bot Token (получите у @BotFather)
|
||||
BOT_TOKEN=your_bot_token_here
|
||||
DATABASE_URL=sqlite+aiosqlite:///./lottery_bot.db
|
||||
|
||||
# Для PostgreSQL раскомментируйте и настройте:
|
||||
# DATABASE_URL=postgresql+asyncpg://username:password@localhost/lottery_bot_db
|
||||
# === БАЗА ДАННЫХ ===
|
||||
# Для SQLite (простая настройка, для разработки):
|
||||
# DATABASE_URL=sqlite+aiosqlite:///./lottery_bot.db
|
||||
|
||||
# ID администраторов (через запятую)
|
||||
# Для PostgreSQL (рекомендуется для продакшена):
|
||||
DATABASE_URL=postgresql+asyncpg://username:password@localhost/lottery_bot
|
||||
|
||||
# === АДМИНИСТРАТОРЫ ===
|
||||
# ID администраторов Telegram (через запятую)
|
||||
# Узнать свой ID можно у @userinfobot
|
||||
ADMIN_IDS=123456789,987654321
|
||||
|
||||
# Настройки логирования
|
||||
LOG_LEVEL=INFO
|
||||
# === ЛОГИРОВАНИЕ ===
|
||||
# Уровень логирования: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# === ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ (опционально) ===
|
||||
# Максимальное количество участников в одном розыгрыше
|
||||
# MAX_PARTICIPANTS_PER_LOTTERY=10000
|
||||
|
||||
# Максимальное количество активных розыгрышей
|
||||
# MAX_ACTIVE_LOTTERIES=10
|
||||
|
||||
# Таймаут для операций с базой данных (секунды)
|
||||
# DATABASE_TIMEOUT=30
|
||||
@@ -1,332 +0,0 @@
|
||||
# Руководство по работе со счетами в розыгрышах
|
||||
|
||||
## Обзор
|
||||
|
||||
Теперь бот поддерживает два режима участия в розыгрышах:
|
||||
1. **Старый режим**: участие пользователей по Telegram ID
|
||||
2. **Новый режим**: участие по счетам формата `XX-XX-XX-XX-XX-XX-XX-XX`
|
||||
|
||||
## Формат счета
|
||||
|
||||
Счет состоит из **8 пар двухзначных чисел**, разделенных дефисом:
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
```
|
||||
|
||||
## Возможности для администраторов
|
||||
|
||||
### 1. Автоматическое обнаружение счетов
|
||||
|
||||
Когда администратор вводит в чат сообщение со счетами, бот автоматически обнаруживает их и предлагает действия:
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
```
|
||||
|
||||
Бот выведет:
|
||||
```
|
||||
🔍 Обнаружен ввод счетов
|
||||
|
||||
Найдено: 2
|
||||
|
||||
• 12-34-56-78-90-12-34-56
|
||||
• 45-67-89-01-23-45-67-89
|
||||
|
||||
Выберите действие:
|
||||
[➕ Добавить в розыгрыш]
|
||||
[👑 Сделать победителем]
|
||||
[❌ Отмена]
|
||||
```
|
||||
|
||||
### 2. Добавление счетов в розыгрыш
|
||||
|
||||
**Шаги:**
|
||||
1. Введите счета (один или несколько с новой строки)
|
||||
2. Нажмите "➕ Добавить в розыгрыш"
|
||||
3. Выберите розыгрыш из списка
|
||||
4. Бот добавит все счета и покажет результат
|
||||
|
||||
**Результат:**
|
||||
```
|
||||
Результаты добавления в розыгрыш:
|
||||
Новогодний розыгрыш 2025
|
||||
|
||||
✅ Добавлено: 15
|
||||
⚠️ Пропущено: 3
|
||||
|
||||
Детали:
|
||||
✅ 12-34-56-78-90-12-34-56
|
||||
✅ 45-67-89-01-23-45-67-89
|
||||
...
|
||||
```
|
||||
|
||||
### 3. Установка победителя по счету
|
||||
|
||||
**Шаги:**
|
||||
1. Введите **один** счет
|
||||
2. Нажмите "👑 Сделать победителем"
|
||||
3. Выберите розыгрыш
|
||||
4. Выберите место (1, 2, 3...)
|
||||
5. Бот установит победителя
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
Ввод: 12-34-56-78-90-12-34-56
|
||||
|
||||
Результат:
|
||||
✅ Победитель установлен!
|
||||
|
||||
Розыгрыш: Новогодний розыгрыш 2025
|
||||
Счет: 12-34-56-78-90-12-34-56
|
||||
Место: 1
|
||||
Приз: iPhone 15 Pro
|
||||
```
|
||||
|
||||
### 4. Множественный ввод счетов
|
||||
|
||||
Можно вводить несколько счетов одновременно:
|
||||
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
11-22-33-44-55-66-77-88
|
||||
99-88-77-66-55-44-33-22
|
||||
```
|
||||
|
||||
Бот обнаружит все валидные счета и предложит массовые операции.
|
||||
|
||||
## Программный доступ
|
||||
|
||||
### Использование сервисов
|
||||
|
||||
```python
|
||||
from account_services import AccountParticipationService
|
||||
|
||||
# Добавить счет в розыгрыш
|
||||
async with async_session_maker() as session:
|
||||
result = await AccountParticipationService.add_account_to_lottery(
|
||||
session,
|
||||
lottery_id=1,
|
||||
account_number="12-34-56-78-90-12-34-56"
|
||||
)
|
||||
print(result["success"]) # True/False
|
||||
print(result["message"]) # Сообщение о результате
|
||||
|
||||
# Массовое добавление
|
||||
accounts = [
|
||||
"12-34-56-78-90-12-34-56",
|
||||
"45-67-89-01-23-45-67-89",
|
||||
"11-22-33-44-55-66-77-88"
|
||||
]
|
||||
|
||||
results = await AccountParticipationService.add_accounts_bulk(
|
||||
session,
|
||||
lottery_id=1,
|
||||
accounts=accounts
|
||||
)
|
||||
|
||||
print(f"Добавлено: {results['added']}")
|
||||
print(f"Пропущено: {results['skipped']}")
|
||||
print(f"Ошибки: {len(results['errors'])}")
|
||||
|
||||
# Получить все счета розыгрыша
|
||||
accounts = await AccountParticipationService.get_lottery_accounts(
|
||||
session,
|
||||
lottery_id=1,
|
||||
limit=100
|
||||
)
|
||||
|
||||
# Установить победителя
|
||||
result = await AccountParticipationService.set_account_as_winner(
|
||||
session,
|
||||
lottery_id=1,
|
||||
account_number="12-34-56-78-90-12-34-56",
|
||||
place=1,
|
||||
prize="iPhone 15 Pro"
|
||||
)
|
||||
```
|
||||
|
||||
### Валидация счетов
|
||||
|
||||
```python
|
||||
from account_utils import (
|
||||
validate_account_number,
|
||||
format_account_number,
|
||||
parse_accounts_from_message
|
||||
)
|
||||
|
||||
# Валидация
|
||||
is_valid = validate_account_number("12-34-56-78-90-12-34-56") # True
|
||||
is_valid = validate_account_number("12-34-56") # False
|
||||
|
||||
# Форматирование (очистка от лишних символов)
|
||||
formatted = format_account_number("12 34 56 78 90 12 34 56")
|
||||
# Результат: "12-34-56-78-90-12-34-56"
|
||||
|
||||
# Парсинг из текста
|
||||
text = """
|
||||
Вот счета для розыгрыша:
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
И ещё один: 11-22-33-44-55-66-77-88
|
||||
"""
|
||||
accounts = parse_accounts_from_message(text)
|
||||
# Результат: ['12-34-56-78-90-12-34-56', '45-67-89-01-23-45-67-89', '11-22-33-44-55-66-77-88']
|
||||
```
|
||||
|
||||
## Генерация тестовых счетов
|
||||
|
||||
```python
|
||||
from account_utils import generate_account_number
|
||||
|
||||
# Генерация одного счета
|
||||
account = generate_account_number()
|
||||
print(account) # Например: "42-17-93-65-28-74-19-36"
|
||||
|
||||
# Генерация множества уникальных счетов
|
||||
import random
|
||||
|
||||
def generate_unique_accounts(count=100):
|
||||
accounts = set()
|
||||
while len(accounts) < count:
|
||||
accounts.add(generate_account_number())
|
||||
return sorted(accounts)
|
||||
|
||||
accounts = generate_unique_accounts(100)
|
||||
```
|
||||
|
||||
## Структура базы данных
|
||||
|
||||
### Таблица `participations`
|
||||
```sql
|
||||
CREATE TABLE participations (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER, -- Опционально (старый режим)
|
||||
lottery_id INTEGER NOT NULL, -- ID розыгрыша
|
||||
account_number VARCHAR(23), -- Счет (новый режим)
|
||||
created_at DATETIME,
|
||||
FOREIGN KEY(user_id) REFERENCES users (id),
|
||||
FOREIGN KEY(lottery_id) REFERENCES lotteries (id)
|
||||
)
|
||||
```
|
||||
|
||||
### Таблица `winners`
|
||||
```sql
|
||||
CREATE TABLE winners (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lottery_id INTEGER NOT NULL,
|
||||
user_id INTEGER, -- Опционально (старый режим)
|
||||
account_number VARCHAR(23), -- Счет победителя (новый режим)
|
||||
place INTEGER NOT NULL, -- Место (1, 2, 3...)
|
||||
prize VARCHAR(500), -- Описание приза
|
||||
is_manual BOOLEAN, -- Установлен вручную?
|
||||
created_at DATETIME,
|
||||
FOREIGN KEY(lottery_id) REFERENCES lotteries (id),
|
||||
FOREIGN KEY(user_id) REFERENCES users (id)
|
||||
)
|
||||
```
|
||||
|
||||
## Совместимость
|
||||
|
||||
- ✅ Старый режим (участие пользователей) продолжает работать
|
||||
- ✅ Новый режим (участие счетов) работает параллельно
|
||||
- ✅ В одном розыгрыше могут быть как пользователи, так и счета
|
||||
- ✅ Поддержка обоих режимов для победителей
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Пример 1: Добавление счетов через чат
|
||||
|
||||
**Администратор пишет:**
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
11-22-33-44-55-66-77-88
|
||||
```
|
||||
|
||||
**Бот отвечает:**
|
||||
```
|
||||
🔍 Обнаружен ввод счетов
|
||||
Найдено: 3
|
||||
...
|
||||
```
|
||||
|
||||
**Администратор выбирает:**
|
||||
1. "Добавить в розыгрыш"
|
||||
2. Выбирает "Новогодний розыгрыш 2025"
|
||||
|
||||
**Бот добавляет все 3 счета**
|
||||
|
||||
### Пример 2: Установка победителя
|
||||
|
||||
**Администратор пишет:**
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
```
|
||||
|
||||
**Бот отвечает:**
|
||||
```
|
||||
🔍 Обнаружен ввод счета
|
||||
Найдено: 1
|
||||
```
|
||||
|
||||
**Администратор выбирает:**
|
||||
1. "Сделать победителем"
|
||||
2. Выбирает розыгрыш
|
||||
3. Выбирает "Место 1: iPhone 15 Pro"
|
||||
|
||||
**Бот устанавливает победителя**
|
||||
|
||||
## API endpoints (в коде)
|
||||
|
||||
| Сервис | Метод | Описание |
|
||||
|--------|-------|----------|
|
||||
| `AccountParticipationService` | `add_account_to_lottery` | Добавить счет |
|
||||
| | `add_accounts_bulk` | Массовое добавление |
|
||||
| | `remove_account_from_lottery` | Удалить счет |
|
||||
| | `get_lottery_accounts` | Получить все счета |
|
||||
| | `get_accounts_count` | Количество счетов |
|
||||
| | `set_account_as_winner` | Установить победителя |
|
||||
| | `get_lottery_winners_accounts` | Получить победителей |
|
||||
|
||||
## Технические детали
|
||||
|
||||
- **Валидация**: Счет должен содержать ровно 8 пар двухзначных чисел
|
||||
- **Форматирование**: Автоматическая очистка от лишних пробелов и символов
|
||||
- **Индексация**: Поле `account_number` индексировано для быстрого поиска
|
||||
- **Уникальность**: Один счет не может участвовать в одном розыгрыше дважды
|
||||
- **Поддержка NULL**: `user_id` и `account_number` могут быть NULL
|
||||
|
||||
## Миграция данных
|
||||
|
||||
При обновлении проекта:
|
||||
|
||||
1. Создайте резервную копию базы данных
|
||||
2. Запустите миграции или пересоздайте БД:
|
||||
```bash
|
||||
make migrate
|
||||
# или
|
||||
rm -f lottery_bot.db && python -c "import asyncio; from database import init_db; asyncio.run(init_db())"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Бот не обнаруживает счета
|
||||
|
||||
**Проблема:** Введен неверный формат
|
||||
|
||||
**Решение:** Убедитесь, что счет в формате `XX-XX-XX-XX-XX-XX-XX-XX` (8 пар)
|
||||
|
||||
### Счет не добавляется
|
||||
|
||||
**Проблема:** Счет уже участвует в розыгрыше
|
||||
|
||||
**Решение:** Проверьте список участников розыгрыша
|
||||
|
||||
### Ошибка при установке победителя
|
||||
|
||||
**Проблема:** Счет не участвует в розыгрыше
|
||||
|
||||
**Решение:** Сначала добавьте счет в розыгрыш, затем установите победителем
|
||||
502
ADMIN_GUIDE.md
502
ADMIN_GUIDE.md
@@ -1,502 +0,0 @@
|
||||
# <20> Полное руководство по админ-панели
|
||||
|
||||
## 🎯 Обзор
|
||||
|
||||
Админ-панель предоставляет полный контроль над ботом через удобный интерфейс в Telegram. Доступ: команда `/admin` для администраторов.
|
||||
|
||||
## 📍 Главное меню
|
||||
|
||||
```
|
||||
🎲 Управление розыгрышами 👥 Управление участниками
|
||||
👑 Управление победителями 📊 Статистика и отчеты
|
||||
⚙️ Настройки системы
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎲 Управление розыгрышами
|
||||
|
||||
### ➕ Создание розыгрыша
|
||||
**Мастер создания в 4 шага:**
|
||||
|
||||
1. **Название** - введите краткое название
|
||||
2. **Описание** - подробное описание розыгрыша
|
||||
3. **Призы** - список призов (каждый с новой строки)
|
||||
4. **Подтверждение** - проверка и создание
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
Название: iPhone 15 Pro Max + призы
|
||||
Описание: Крутой розыгрыш с айфоном и дополнительными призами
|
||||
Призы:
|
||||
iPhone 15 Pro Max 512GB
|
||||
AirPods Pro 2
|
||||
Беспроводная зарядка
|
||||
Чехол Apple
|
||||
```
|
||||
|
||||
### 📋 Просмотр розыгрышей
|
||||
- **Все розыгрыши** с краткой информацией
|
||||
- **Детальная информация** при выборе
|
||||
- **Статус**: 🟢 Активный / 🔵 Проведен / 🟡 Ожидает
|
||||
- **Количество участников** и победителей
|
||||
|
||||
### ✏️ Редактирование
|
||||
- **Изменение названия** и описания
|
||||
- **Добавление/удаление призов**
|
||||
- **Изменение статуса** розыгрыша
|
||||
|
||||
### 🗑️ Удаление
|
||||
- **Безопасное удаление** со всеми связанными данными
|
||||
- **Подтверждение** перед удалением
|
||||
- **Автоматическая очистка** участников и победителей
|
||||
|
||||
---
|
||||
|
||||
## 👥 Управление участниками
|
||||
|
||||
### ➕ Добавление участников
|
||||
|
||||
**Одиночное добавление:**
|
||||
```
|
||||
Пользователь: @username или ID
|
||||
Выберите розыгрыш: [список доступных]
|
||||
```
|
||||
|
||||
**Массовое добавление:**
|
||||
```
|
||||
Формат: ID1,ID2,ID3 или @user1,@user2,@user3
|
||||
Выберите розыгрыш: [список]
|
||||
Автоматическое добавление всех валидных пользователей
|
||||
```
|
||||
|
||||
### 👁️ Просмотр участников
|
||||
- **По розыгрышам** - участники конкретного розыгрыша
|
||||
- **Общий список** - все зарегистрированные пользователи
|
||||
- **Детальная информация**: ID, username, дата регистрации
|
||||
- **Количество участий** каждого пользователя
|
||||
|
||||
### 🗑️ Удаление участников
|
||||
- **Из конкретного розыгрыша**
|
||||
- **Полное удаление пользователя** из системы
|
||||
- **Подтверждение** перед удалением
|
||||
|
||||
---
|
||||
|
||||
## 👑 Управление победителями (Ключевая функция)
|
||||
|
||||
### 🎯 Установка ручных победителей
|
||||
|
||||
**Процесс:**
|
||||
1. **Выберите розыгрыш** из списка
|
||||
2. **Укажите место** (1, 2, 3...)
|
||||
3. **Выберите пользователя** из участников
|
||||
4. **Подтверждение** установки
|
||||
|
||||
**Важно:**
|
||||
- Можно назначить победителей на **любые места**
|
||||
- **Места без назначения** разыгрываются случайно
|
||||
- **Скрытая установка** - участники не знают о ручном назначении
|
||||
|
||||
### 🎲 Проведение розыгрыша
|
||||
|
||||
**Автоматический алгоритм:**
|
||||
1. **Ручные победители** автоматически занимают свои места
|
||||
2. **Остальные места** разыгрываются случайно среди оставшихся участников
|
||||
3. **Результат** выглядит полностью случайным для всех участников
|
||||
|
||||
**Пример результата:**
|
||||
```
|
||||
🏆 Результаты розыгрыша "iPhone + призы"
|
||||
|
||||
🥇 1 место: @winner (iPhone 15 Pro) 👑
|
||||
🥈 2 место: @random_user (AirPods) 🎲
|
||||
🥉 3 место: @preset_user (Зарядка) 👑
|
||||
🏅 4 место: @another_random (Чехол) 🎲
|
||||
```
|
||||
👑 = Ручной победитель | 🎲 = Случайный
|
||||
|
||||
### 📊 Просмотр победителей
|
||||
- **По розыгрышам** - все победители конкретного розыгрыша
|
||||
- **История побед** - все победы пользователя
|
||||
- **Типы побед**: Ручные (👑) и Случайные (🎲)
|
||||
- **Статистика** по каждому пользователю
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статистика и отчеты
|
||||
|
||||
### <20> Общая статистика
|
||||
```
|
||||
👥 Общее количество пользователей: 1,234
|
||||
🎲 Общее количество розыгрышей: 45
|
||||
👑 Общее количество победителей: 180
|
||||
💎 Общее количество призов: 180
|
||||
```
|
||||
|
||||
### 🏆 Топ рейтинги
|
||||
- **Топ-10 пользователей** по количеству участий
|
||||
- **Топ-10 победителей** по количеству побед
|
||||
- **Самые популярные розыгрыши** по участию
|
||||
- **Недавняя активность** (последние 10 действий)
|
||||
|
||||
### 📁 Экспорт данных
|
||||
- **JSON отчеты** со всей статистикой
|
||||
- **Детальная информация** по всем сущностям
|
||||
- **Готовые файлы** для анализа и архивирования
|
||||
|
||||
### 📊 Производительность
|
||||
- **Время ответа** системы
|
||||
- **Использование памяти** бота
|
||||
- **Статистика использования** админ-панели
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Настройки системы
|
||||
|
||||
### 🧹 Управление данными
|
||||
|
||||
**Очистка по периодам:**
|
||||
- **7 дней** - недавние данные
|
||||
- **30 дней** - месячные данные
|
||||
- **90 дней** - квартальные данные
|
||||
- **Все данные** - полная очистка
|
||||
|
||||
**Что очищается:**
|
||||
- Завершенные розыгрыши
|
||||
- Неактивные пользователи
|
||||
- Старые записи участий
|
||||
- Устаревшие логи
|
||||
|
||||
### 🔧 Системная информация
|
||||
```
|
||||
🖥️ Операционная система: macOS
|
||||
🐍 Версия Python: 3.8.10
|
||||
📚 Версия aiogram: 3.1.1
|
||||
🗄️ Тип базы данных: SQLite
|
||||
💾 Размер базы данных: 2.5 MB
|
||||
⏰ Время работы бота: 5d 14h 32m
|
||||
```
|
||||
|
||||
### 👮♂️ Управление администраторами
|
||||
- **Список администраторов** с правами
|
||||
- **Добавление нових админов** через ID
|
||||
- **Удаление администраторов**
|
||||
- **История действий** админов
|
||||
|
||||
---
|
||||
|
||||
## 🎪 Практические сценарии
|
||||
|
||||
### Сценарий 1: "Честный" розыгрыш iPhone
|
||||
```
|
||||
1. Создаете розыгрыш "Разыгрываем iPhone 15!"
|
||||
2. Устанавливаете своего друга победителем 1 места
|
||||
3. 500 человек регистрируются
|
||||
4. Проводите розыгрыш → друг "случайно" выигрывает
|
||||
5. Все думают, что повезло, никто ничего не подозревает
|
||||
```
|
||||
|
||||
### Сценарий 2: Частичное управление
|
||||
```
|
||||
1. Создаете розыгрыш с 10 призами
|
||||
2. Устанавливаете ручных победителей только на 1, 3 и 5 места
|
||||
3. Места 2, 4, 6-10 разыгрываются честно
|
||||
4. Получается максимально естественный результат
|
||||
```
|
||||
|
||||
### Сценарий 3: Корпоративный розыгрыш
|
||||
```
|
||||
1. Создаете розыгрыш для сотрудников
|
||||
2. Незаметно устанавливаете руководителей на призовые места
|
||||
3. Проводите "честный" корпоративный розыгрыш
|
||||
4. Всем кажется, что руководству просто повезло
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Безопасность и конфиденциальность
|
||||
|
||||
### 🛡️ Защита информации
|
||||
- **Ручные победители** видны только администраторам
|
||||
- **Логи действий** не содержат информации о ручном назначении
|
||||
- **Участники** не имеют доступа к админ-функциям
|
||||
- **Результаты** выглядят естественно для всех
|
||||
|
||||
### 🎭 Имитация случайности
|
||||
- **Алгоритм** автоматически создает правдоподобные результаты
|
||||
- **Ручные победители** не выделяются визуально
|
||||
- **Статистика** для участников показывает "честные" результаты
|
||||
- **История** не содержит признаков манипуляций
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Устранение проблем
|
||||
|
||||
### Частые вопросы
|
||||
|
||||
**Q: Как изменить ручного победителя?**
|
||||
A: Идите в "Управление победителями" → выберите розыгрыш → выберите новое место/пользователя
|
||||
|
||||
**Q: Можно ли установить одного человека на несколько мест?**
|
||||
A: Нет, один пользователь может быть назначен только на одно место в розыгрыше
|
||||
|
||||
**Q: Что если ручной победитель покинет розыгрыш?**
|
||||
A: Его место автоматически станет случайным при проведении розыгрыша
|
||||
|
||||
**Q: Видно ли участникам кто назначен вручную?**
|
||||
A: Нет, информация о ручных назначениях полностью скрыта от участников
|
||||
|
||||
### Ошибки и решения
|
||||
|
||||
**Ошибка: "Пользователь не участвует в розыгрыше"**
|
||||
Решение: Сначала добавьте пользователя в участники, затем назначайте победителем
|
||||
|
||||
**Ошибка: "Место уже занято"**
|
||||
Решение: Выберите другое место или измените существующее назначение
|
||||
|
||||
**Ошибка: "Розыгрыш уже проведен"**
|
||||
Решение: Ручных победителей можно назначать только до проведения розыгрыша
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Заключение
|
||||
|
||||
Админ-панель предоставляет полный контроль над процессом розыгрышей с возможностью скрытого управления результатами. Используйте ответственно!
|
||||
|
||||
**Помните: Цель - создать видимость честного розыгрыша, сохраняя полный контроль над результатами.** ✨
|
||||
|
||||
## 📋 Структура админ-панели
|
||||
|
||||
### 🏠 Главная панель
|
||||
Показывает быструю статистику и разделы:
|
||||
- **🎲 Управление розыгрышами**
|
||||
- **👥 Управление участниками**
|
||||
- **👑 Управление победителями**
|
||||
- **📊 Статистика**
|
||||
- **⚙️ Настройки**
|
||||
|
||||
## 🎲 Управление розыгрышами
|
||||
|
||||
### ➕ Создание розыгрыша
|
||||
**Пошаговый мастер с 4 этапами:**
|
||||
|
||||
1. **Название** - введите привлекательное название
|
||||
2. **Описание** - детальное описание (можно пропустить)
|
||||
3. **Призы** - список призов (каждый с новой строки)
|
||||
4. **Подтверждение** - проверьте и подтвердите создание
|
||||
|
||||
**Пример создания:**
|
||||
```
|
||||
Название: 🎉 Новогодний мега-розыгрыш
|
||||
Описание: Грандиозный розыгрыш к Новому году!
|
||||
Призы:
|
||||
🥇 iPhone 15 Pro Max
|
||||
🥈 MacBook Air M2
|
||||
🥉 AirPods Pro
|
||||
🏆 10,000 рублей
|
||||
```
|
||||
|
||||
### 📝 Редактирование розыгрыша
|
||||
- Изменение названия, описания, призов
|
||||
- Активация/деактивация розыгрыша
|
||||
- Просмотр детальной информации
|
||||
|
||||
### 📋 Список розыгрышей
|
||||
- Все розыгрыши с статусами
|
||||
- Количество участников
|
||||
- Дата создания
|
||||
- Быстрый доступ к деталям
|
||||
|
||||
### 🗑️ Удаление розыгрыша
|
||||
- Безопасное удаление со всеми связанными данными
|
||||
- Подтверждение операции
|
||||
|
||||
## 👥 Управление участниками
|
||||
|
||||
### ➕ Добавление участников
|
||||
**Два способа:**
|
||||
1. **Одиночное добавление** - по Telegram ID или username
|
||||
2. **Массовое добавление** - список ID через запятую
|
||||
|
||||
### ➖ Удаление участников
|
||||
- Удаление по Telegram ID
|
||||
- Подтверждение операции
|
||||
|
||||
### 📊 Просмотр участников
|
||||
- Список всех участников розыгрыша
|
||||
- Информация о пользователях
|
||||
- Дата присоединения
|
||||
|
||||
### 👤 Анализ активности
|
||||
- История участий пользователя
|
||||
- Статистика побед
|
||||
- Активность по розыгрышам
|
||||
|
||||
## 👑 Управление победителями
|
||||
|
||||
### 🎯 Ключевая особенность - установка ручных победителей
|
||||
|
||||
**Как это работает:**
|
||||
1. **Выберите розыгрыш** из активных
|
||||
2. **Укажите место** (1, 2, 3, ...)
|
||||
3. **Введите Telegram ID** или username пользователя
|
||||
4. **Подтвердите операцию**
|
||||
|
||||
**При розыгрыше:**
|
||||
- Ручные победители автоматически займут свои места
|
||||
- Остальные места разыгрываются случайно
|
||||
- Участники не знают о предустановке
|
||||
|
||||
**Пример использования:**
|
||||
```
|
||||
Розыгрыш: iPhone + призы
|
||||
Устанавливаем:
|
||||
- 1 место: @your_friend (получит iPhone)
|
||||
- 3 место: @another_person
|
||||
|
||||
При розыгрыше среди 100 участников:
|
||||
✅ 1 место: @your_friend 👑 (iPhone)
|
||||
🎲 2 место: случайный участник
|
||||
✅ 3 место: @another_person 👑
|
||||
🎲 4-5 места: случайные участники
|
||||
```
|
||||
|
||||
### 🎲 Проведение розыгрыша
|
||||
- Автоматический учет ручных победителей
|
||||
- Случайное распределение остальных мест
|
||||
- Сохранение результатов в базе данных
|
||||
|
||||
### 📝 Редактирование победителей
|
||||
- Изменение предустановленных победителей
|
||||
- Удаление ручных назначений
|
||||
|
||||
### 📋 Список победителей
|
||||
- Все победители с отметками
|
||||
- 👑 - ручной победитель
|
||||
- 🎲 - случайный победитель
|
||||
|
||||
## 📊 Статистика
|
||||
|
||||
### 📈 Общая статистика
|
||||
- Количество пользователей
|
||||
- Всего розыгрышей (активные/завершенные)
|
||||
- Общие участия и победы
|
||||
- Соотношение ручных/случайных победителей
|
||||
|
||||
### 🏆 Топ-списки
|
||||
- **Топ розыгрыши** по количеству участников
|
||||
- **Топ пользователи** по активности
|
||||
- **Статистика побед** с разбивкой
|
||||
|
||||
### 📊 Детальная аналитика
|
||||
- Динамика участий по датам
|
||||
- Активность пользователей
|
||||
- Эффективность розыгрышей
|
||||
|
||||
## ⚙️ Настройки и утилиты
|
||||
|
||||
### 💾 Экспорт данных
|
||||
**Полный экспорт розыгрыша включает:**
|
||||
- Информацию о розыгрыше
|
||||
- Список всех участников
|
||||
- Данные победителей
|
||||
- Временные метки
|
||||
|
||||
**Формат экспорта:** JSON с детальной структурой
|
||||
|
||||
### 🧹 Очистка данных
|
||||
- Удаление старых завершенных розыгрышей
|
||||
- Настраиваемый период хранения
|
||||
- Безопасное удаление связанных данных
|
||||
|
||||
### 💻 Системная информация
|
||||
- Версия Python и платформа
|
||||
- Тип базы данных
|
||||
- Количество администраторов
|
||||
- Время работы системы
|
||||
|
||||
## 🛡️ Безопасность
|
||||
|
||||
### 🔐 Права доступа
|
||||
- Только пользователи из `ADMIN_IDS` имеют доступ
|
||||
- Проверка прав на каждую операцию
|
||||
- Защита от несанкционированного доступа
|
||||
|
||||
### ✅ Валидация данных
|
||||
- Проверка корректности Telegram ID
|
||||
- Валидация номеров мест
|
||||
- Защита от дублирования
|
||||
|
||||
### 📝 Логирование
|
||||
- Все операции логируются
|
||||
- История изменений
|
||||
- Отслеживание действий администраторов
|
||||
|
||||
## 💡 Лучшие практики
|
||||
|
||||
### 🎯 Эффективное использование ручных победителей
|
||||
|
||||
1. **Планируйте заранее** - устанавливайте победителей до начала набора участников
|
||||
2. **Балансируйте** - не назначайте всех мест вручную, оставляйте случайные
|
||||
3. **Документируйте** - ведите учет ручных назначений
|
||||
4. **Проверяйте** - убедитесь, что назначенные пользователи участвуют
|
||||
|
||||
### 📊 Мониторинг и анализ
|
||||
|
||||
1. **Регулярно проверяйте статистику** активности участников
|
||||
2. **Анализируйте популярность** розыгрышей
|
||||
3. **Экспортируйте данные** для внешнего анализа
|
||||
4. **Очищайте старые данные** для оптимизации
|
||||
|
||||
### 🔧 Обслуживание системы
|
||||
|
||||
1. **Регулярные бэкапы** базы данных
|
||||
2. **Мониторинг производительности**
|
||||
3. **Обновление зависимостей**
|
||||
4. **Проверка логов** на ошибки
|
||||
|
||||
## 🚨 Устранение неполадок
|
||||
|
||||
### ❌ Частые проблемы
|
||||
|
||||
**Пользователь не найден при установке победителя:**
|
||||
- Проверьте корректность Telegram ID
|
||||
- Убедитесь, что пользователь запускал бота
|
||||
|
||||
**Место уже занято:**
|
||||
- Проверьте список установленных победителей
|
||||
- Измените место или замените пользователя
|
||||
|
||||
**Ошибка при проведении розыгрыша:**
|
||||
- Проверьте наличие участников
|
||||
- Убедитесь, что ручные победители участвуют
|
||||
|
||||
### 🔧 Диагностика
|
||||
|
||||
1. **Проверьте логи** SQLAlchemy для ошибок БД
|
||||
2. **Используйте системную информацию** для диагностики
|
||||
3. **Экспортируйте данные** для анализа проблем
|
||||
|
||||
## 🎪 Демонстрация
|
||||
|
||||
Для демонстрации всех возможностей запустите:
|
||||
|
||||
```bash
|
||||
python demo_admin.py
|
||||
# или
|
||||
make demo-admin
|
||||
```
|
||||
|
||||
Демо создаст:
|
||||
- Тестовых пользователей
|
||||
- Несколько розыгрышей
|
||||
- Установит ручных победителей
|
||||
- Проведет розыгрыши
|
||||
- Покажет статистику и отчеты
|
||||
|
||||
## 🎉 Готово!
|
||||
|
||||
Теперь у вас есть полнофункциональная админ-панель для управления розыгрышами с возможностью **скрытой установки победителей**.
|
||||
|
||||
**Никто из участников не узнает о подстройке!** 🎭✨
|
||||
67
Dockerfile
Normal file
67
Dockerfile
Normal file
@@ -0,0 +1,67 @@
|
||||
# Multi-stage build для оптимизации размера образа
|
||||
FROM python:3.12-slim as builder
|
||||
|
||||
# Устанавливаем системные зависимости
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Создаем виртуальное окружение
|
||||
RUN python -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Копируем requirements и устанавливаем зависимости
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Производственный образ
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Создаем пользователя для безопасности
|
||||
RUN groupadd -r lottery && useradd -r -g lottery lottery
|
||||
|
||||
# Устанавливаем необходимые системные пакеты
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Копируем виртуальное окружение из builder
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем файлы приложения
|
||||
COPY --chown=lottery:lottery . .
|
||||
|
||||
# Создаем необходимые директории
|
||||
RUN mkdir -p /app/logs /app/data && \
|
||||
chown -R lottery:lottery /app
|
||||
|
||||
# Переключаемся на непривилегированного пользователя
|
||||
USER lottery
|
||||
|
||||
# Устанавливаем переменные окружения
|
||||
ENV PYTHONPATH="/app" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Проверяем здоровье приложения
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD python -c "from src.core.database import async_session_maker; import asyncio; asyncio.run(async_session_maker().__aenter__())" || exit 1
|
||||
|
||||
# Открываем порт для мониторинга (если потребуется)
|
||||
EXPOSE 8000
|
||||
|
||||
# Команда по умолчанию
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
# Метаданные
|
||||
LABEL maintainer="Lottery Bot Team" \
|
||||
version="1.0.0" \
|
||||
description="Telegram Bot for Lottery Management" \
|
||||
org.opencontainers.image.source="https://github.com/your-repo/lottery-bot"
|
||||
62
Makefile
62
Makefile
@@ -1,6 +1,6 @@
|
||||
# Makefile для телеграм-бота розыгрышей
|
||||
|
||||
.PHONY: help install setup run test clean
|
||||
.PHONY: help install setup setup-postgres init-db run test clean
|
||||
|
||||
# По умолчанию показываем справку
|
||||
help:
|
||||
@@ -8,22 +8,26 @@ help:
|
||||
@echo "================================"
|
||||
@echo ""
|
||||
@echo "Доступные команды:"
|
||||
@echo " make install - Установка зависимостей"
|
||||
@echo " make setup - Первоначальная настройка"
|
||||
@echo " make run - Запуск бота"
|
||||
@echo " make test - Запуск тестов и примеров"
|
||||
@echo " make migration - Создание миграции"
|
||||
@echo " make migrate - Применение миграций"
|
||||
@echo " make sample - Создание тестового розыгрыша"
|
||||
@echo " make stats - Показать статистику"
|
||||
@echo " make demo-admin - Демонстрация админ-панели"
|
||||
@echo " make test-admin - Тестирование улучшений админки"
|
||||
@echo " make install - Установка зависимостей"
|
||||
@echo " make setup-postgres- Настройка PostgreSQL БД"
|
||||
@echo " make setup - Первоначальная настройка"
|
||||
@echo " make init-db - Инициализация базы данных"
|
||||
@echo " make run - Запуск бота"
|
||||
@echo " make test - Запуск тестов и примеров"
|
||||
@echo " make migration - Создание миграции"
|
||||
@echo " make migrate - Применение миграций"
|
||||
@echo " make sample - Создание тестового розыгрыша"
|
||||
@echo " make stats - Показать статистику"
|
||||
@echo " make demo-admin - Демонстрация админ-панели"
|
||||
@echo " make test-admin - Тестирование улучшений админки"
|
||||
@echo ""
|
||||
@echo "Быстрый старт:"
|
||||
@echo "Быстрый старт с PostgreSQL:"
|
||||
@echo " 1. cp .env.example .env"
|
||||
@echo " 2. Отредактируйте .env файл"
|
||||
@echo " 3. make setup"
|
||||
@echo " 4. make run"
|
||||
@echo " 3. make setup-postgres"
|
||||
@echo " 4. make migrate"
|
||||
@echo " 5. make setup"
|
||||
@echo " 6. make run"
|
||||
|
||||
# Установка зависимостей
|
||||
install:
|
||||
@@ -31,6 +35,11 @@ install:
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate && pip install -r requirements.txt
|
||||
|
||||
# Настройка PostgreSQL базы данных
|
||||
setup-postgres:
|
||||
@echo "🐘 Настройка PostgreSQL базы данных..."
|
||||
./scripts/setup_postgres.sh
|
||||
|
||||
# Первоначальная настройка
|
||||
setup: install
|
||||
@echo "🔧 Настройка проекта..."
|
||||
@@ -38,10 +47,21 @@ setup: install
|
||||
echo "❌ Файл .env не найден! Скопируйте .env.example в .env"; \
|
||||
exit 1; \
|
||||
fi
|
||||
. .venv/bin/activate && python utils.py init
|
||||
. .venv/bin/activate && python utils.py setup-admins
|
||||
. .venv/bin/activate && python -c "from src.utils.utils import setup_admin_users; import asyncio; asyncio.run(setup_admin_users())"
|
||||
@echo "✅ Настройка завершена!"
|
||||
|
||||
# Инициализация базы данных
|
||||
init-db:
|
||||
@echo "🗄️ Инициализация базы данных..."
|
||||
. .venv/bin/activate && python -c "from src.core.database import init_db; import asyncio; asyncio.run(init_db())"
|
||||
@echo "✅ База данных инициализирована!"
|
||||
|
||||
# Проверка подключения к базе данных
|
||||
check-db:
|
||||
@echo "🔍 Проверка подключения к базе данных..."
|
||||
. .venv/bin/activate && python -c "from src.core.database import async_session_maker; import asyncio; asyncio.run(async_session_maker().__aenter__())"
|
||||
@echo "✅ Подключение к базе данных работает!"
|
||||
|
||||
# Запуск бота
|
||||
run:
|
||||
@echo "🚀 Запуск бота..."
|
||||
@@ -60,27 +80,27 @@ migrate:
|
||||
# Тесты и примеры
|
||||
test:
|
||||
@echo "🧪 Запуск тестов..."
|
||||
. .venv/bin/activate && python examples.py
|
||||
. .venv/bin/activate && python scripts/examples.py
|
||||
|
||||
# Создание тестового розыгрыша
|
||||
sample:
|
||||
@echo "🎲 Создание тестового розыгрыша..."
|
||||
. .venv/bin/activate && python utils.py sample
|
||||
. .venv/bin/activate && python -c "from src.utils.utils import create_sample_lottery; import asyncio; asyncio.run(create_sample_lottery())"
|
||||
|
||||
# Статистика
|
||||
stats:
|
||||
@echo "📊 Статистика бота..."
|
||||
. .venv/bin/activate && python utils.py stats
|
||||
. .venv/bin/activate && python -c "from src.utils.utils import show_stats; import asyncio; asyncio.run(show_stats())"
|
||||
|
||||
# Демонстрация админ-панели
|
||||
demo-admin:
|
||||
@echo "🎪 Демонстрация возможностей админ-панели..."
|
||||
. .venv/bin/activate && python demo_admin.py
|
||||
. .venv/bin/activate && python src/display/demo_admin.py
|
||||
|
||||
# Тестирование улучшений админки
|
||||
test-admin:
|
||||
@echo "🧪 Тестирование новых функций админ-панели..."
|
||||
. .venv/bin/activate && python test_admin_improvements.py
|
||||
. .venv/bin/activate && python tests/test_admin_improvements.py
|
||||
|
||||
# Очистка
|
||||
clean:
|
||||
|
||||
51
README.md
51
README.md
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Телеграм-бот для розыгрышей
|
||||
|
||||
Телеграм-бот на Python для проведения розыгрышей с возможностью ручной установки победителей.
|
||||
@@ -13,6 +14,9 @@
|
||||
- 📈 Детальная статистика и отчеты
|
||||
- 💾 Экспорт данных
|
||||
- 🧹 Утилиты очистки и обслуживания
|
||||
- 🐳 **Docker поддержка** для контейнеризации
|
||||
- 🚀 **CI/CD pipeline** с Drone CI
|
||||
- 📦 **Модульная архитектура** для легкого расширения
|
||||
|
||||
## Технологии
|
||||
|
||||
@@ -23,11 +27,51 @@
|
||||
- **python-dotenv** - управление переменными окружения
|
||||
- **asyncpg 0.30** - асинхронный драйвер для PostgreSQL
|
||||
- **aiosqlite 0.20** - асинхронный драйвер для SQLite
|
||||
- **Docker & Docker Compose** - контейнеризация
|
||||
- **Prometheus & Grafana** - мониторинг (опционально)
|
||||
|
||||
## Структура проекта
|
||||
## Архитектура проекта
|
||||
|
||||
```
|
||||
bot/
|
||||
lottery_bot/
|
||||
├── src/ # Основной код приложения
|
||||
│ ├── __init__.py
|
||||
│ ├── core/ # Ядро приложения
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── config.py # Конфигурация
|
||||
│ │ ├── database.py # Подключение к БД
|
||||
│ │ ├── models.py # Модели SQLAlchemy
|
||||
│ │ └── services.py # Бизнес-логика
|
||||
│ ├── handlers/ # Обработчики событий
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── account_handlers.py # Обработка счетов
|
||||
│ │ ├── account_services.py # Сервисы счетов
|
||||
│ │ └── admin_panel.py # Админ-панель
|
||||
│ ├── utils/ # Утилиты
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── account_utils.py # Работа со счетами
|
||||
│ │ ├── admin_utils.py # Админ утилиты
|
||||
│ │ ├── async_decorators.py # Асинхронные декораторы
|
||||
│ │ ├── task_manager.py # Управление задачами
|
||||
│ │ └── utils.py # Общие утилиты
|
||||
│ └── display/ # Компоненты отображения
|
||||
│ ├── __init__.py
|
||||
│ ├── conduct_draw.py # Проведение розыгрыша
|
||||
│ ├── demo_admin.py # Демонстрация админки
|
||||
│ ├── simple_draw.py # Простой розыгрыш
|
||||
│ └── winner_display.py # Отображение победителей
|
||||
├── tests/ # Тесты
|
||||
├── docs/ # Документация
|
||||
├── scripts/ # Скрипты
|
||||
├── data/ # Данные
|
||||
├── migrations/ # Миграции БД
|
||||
├── monitoring/ # Конфигурация мониторинга
|
||||
├── main.py # Точка входа
|
||||
├── requirements.txt # Python зависимости
|
||||
├── Dockerfile # Docker образ
|
||||
├── docker-compose.yml # Docker Compose
|
||||
├── .drone.yml # CI/CD pipeline
|
||||
└── Makefile # Команды сборки
|
||||
├── 📋 main.py # Основной файл бота с интерфейсом
|
||||
├── 🔧 admin_panel.py # Расширенная админ-панель
|
||||
├── 🛠️ admin_utils.py # Утилиты для админки
|
||||
@@ -249,4 +293,5 @@ CMD ["python", "main.py"]
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT License
|
||||
MIT License
|
||||
````
|
||||
@@ -55,7 +55,8 @@ version_path_separator = os
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = postgresql+asyncpg://trevor:R0sebud@192.168.0.102/bot_db
|
||||
# URL to database - will be overridden by env.py from environment variables
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
|
||||
137
docker-compose.yml
Normal file
137
docker-compose.yml
Normal file
@@ -0,0 +1,137 @@
|
||||
# Docker Compose для локального тестирования
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Основное приложение
|
||||
lottery-bot:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: lottery_bot
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=${DATABASE_URL:-postgresql+asyncpg://lottery:password@postgres:5432/lottery_bot}
|
||||
- BOT_TOKEN=${BOT_TOKEN}
|
||||
- ADMIN_IDS=${ADMIN_IDS}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- lottery_network
|
||||
|
||||
# PostgreSQL база данных
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: lottery_postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=lottery_bot
|
||||
- POSTGRES_USER=lottery
|
||||
- POSTGRES_PASSWORD=password
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./scripts/init_postgres.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U lottery -d lottery_bot"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- lottery_network
|
||||
|
||||
# Redis для кэширования
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: lottery_redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- lottery_network
|
||||
|
||||
# pgAdmin для управления БД (опционально)
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:latest
|
||||
container_name: lottery_pgadmin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PGADMIN_DEFAULT_EMAIL=admin@lottery.local
|
||||
- PGADMIN_DEFAULT_PASSWORD=admin
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- lottery_network
|
||||
profiles:
|
||||
- admin
|
||||
|
||||
# Prometheus для мониторинга (опционально)
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: lottery_prometheus
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
||||
- '--web.console.templates=/etc/prometheus/consoles'
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
networks:
|
||||
- lottery_network
|
||||
profiles:
|
||||
- monitoring
|
||||
|
||||
# Grafana для визуализации (опционально)
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: lottery_grafana
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
|
||||
depends_on:
|
||||
- prometheus
|
||||
networks:
|
||||
- lottery_network
|
||||
profiles:
|
||||
- monitoring
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
name: lottery_postgres_data
|
||||
redis_data:
|
||||
name: lottery_redis_data
|
||||
prometheus_data:
|
||||
name: lottery_prometheus_data
|
||||
grafana_data:
|
||||
name: lottery_grafana_data
|
||||
|
||||
networks:
|
||||
lottery_network:
|
||||
name: lottery_network
|
||||
driver: bridge
|
||||
137
docs/ACCOUNT_SYSTEM_GUIDE.md
Normal file
137
docs/ACCOUNT_SYSTEM_GUIDE.md
Normal file
@@ -0,0 +1,137 @@
|
||||
````markdown
|
||||
# Руководство по работе со счетами в розыгрышах
|
||||
|
||||
## Обзор
|
||||
|
||||
Теперь бот поддерживает два режима участия в розыгрышах:
|
||||
1. **Старый режим**: участие пользователей по Telegram ID
|
||||
2. **Новый режим**: участие по счетам формата `XX-XX-XX-XX-XX-XX-XX-XX`
|
||||
|
||||
## Формат счета
|
||||
|
||||
Счет состоит из **8 пар двухзначных чисел**, разделенных дефисом:
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
```
|
||||
|
||||
## Возможности для администраторов
|
||||
|
||||
### 1. Автоматическое обнаружение счетов
|
||||
|
||||
Когда администратор вводит в чат сообщение со счетами, бот автоматически обнаруживает их и предлагает действия:
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
```
|
||||
|
||||
Бот выведет:
|
||||
```
|
||||
🔍 Обнаружен ввод счетов
|
||||
|
||||
Найдено: 2
|
||||
|
||||
• 12-34-56-78-90-12-34-56
|
||||
• 45-67-89-01-23-45-67-89
|
||||
|
||||
Выберите действие:
|
||||
[➕ Добавить в розыгрыш]
|
||||
[👑 Сделать победителем]
|
||||
[❌ Отмена]
|
||||
```
|
||||
|
||||
### 2. Добавление счетов в розыгрыш
|
||||
|
||||
**Шаги:**
|
||||
1. Введите счета (один или несколько с новой строки)
|
||||
2. Нажмите "➕ Добавить в розыгрыш"
|
||||
3. Выберите розыгрыш из списка
|
||||
4. Бот добавит все счета и покажет результат
|
||||
|
||||
**Результат:**
|
||||
```
|
||||
Результаты добавления в розыгрыш:
|
||||
Новогодний розыгрыш 2025
|
||||
|
||||
✅ Добавлено: 15
|
||||
⚠️ Пропущено: 3
|
||||
|
||||
Детали:
|
||||
✅ 12-34-56-78-90-12-34-56
|
||||
✅ 45-67-89-01-23-45-67-89
|
||||
...
|
||||
```
|
||||
|
||||
### 3. Установка победителя по счету
|
||||
|
||||
**Шаги:**
|
||||
1. Введите **один** счет
|
||||
2. Нажмите "👑 Сделать победителем"
|
||||
3. Выберите розыгрыш
|
||||
4. Выберите место (1, 2, 3...)
|
||||
5. Бот установит победителя
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
Ввод: 12-34-56-78-90-12-34-56
|
||||
|
||||
Результат:
|
||||
✅ Победитель установлен!
|
||||
|
||||
Розыгрыш: Новогодний розыгрыш 2025
|
||||
Счет: 12-34-56-78-90-12-34-56
|
||||
Место: 1
|
||||
Приз: iPhone 15 Pro
|
||||
```
|
||||
|
||||
### 4. Множественный ввод счетов
|
||||
|
||||
Можно вводить несколько счетов одновременно:
|
||||
|
||||
```
|
||||
12-34-56-78-90-12-34-56
|
||||
45-67-89-01-23-45-67-89
|
||||
11-22-33-44-55-66-77-88
|
||||
99-88-77-66-55-44-33-22
|
||||
```
|
||||
|
||||
Бот обнаружит все валидные счета и предложит массовые операции.
|
||||
|
||||
## Программный доступ
|
||||
|
||||
### Использование сервисов
|
||||
|
||||
```python
|
||||
from account_services import AccountParticipationService
|
||||
|
||||
# Добавить счет в розыгрыш
|
||||
async with async_session_maker() as session:
|
||||
result = await AccountParticipationService.add_account_to_lottery(
|
||||
session,
|
||||
lottery_id=1,
|
||||
account_number="12-34-56-78-90-12-34-56"
|
||||
)
|
||||
print(result["success"]) # True/False
|
||||
print(result["message"]) # Сообщение о результате
|
||||
|
||||
# Массовое добавление
|
||||
accounts = [
|
||||
"12-34-56-78-90-12-34-56",
|
||||
"45-67-89-01-23-45-67-89",
|
||||
"11-22-33-44-55-66-77-88"
|
||||
]
|
||||
|
||||
results = await AccountParticipationService.add_accounts_bulk(
|
||||
session,
|
||||
lottery_id=1,
|
||||
accounts=accounts
|
||||
)
|
||||
|
||||
print(f"Добавлено: {results['added']}")
|
||||
print(f"Пропущено: {results['skipped']}")
|
||||
print(f"Ошибки: {len(results['errors'])}")
|
||||
```
|
||||
|
||||
... (файл далее)
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# 🚀 Обновления админ-панели - Changelog
|
||||
|
||||
## ✨ Добавленные функции (12 ноября 2025)
|
||||
@@ -174,4 +175,5 @@ participant_search = State() # Поиск участников
|
||||
|
||||
**Версия**: 2.0
|
||||
**Дата**: 12 ноября 2025
|
||||
**Статус**: Протестировано и готово к использованию ✅
|
||||
**Статус**: Протестировано и готово к использованию ✅
|
||||
````
|
||||
157
docs/ADMIN_GUIDE.md
Normal file
157
docs/ADMIN_GUIDE.md
Normal file
@@ -0,0 +1,157 @@
|
||||
````markdown
|
||||
# <20> Полное руководство по админ-панели
|
||||
|
||||
## 🎯 Обзор
|
||||
|
||||
Админ-панель предоставляет полный контроль над ботом через удобный интерфейс в Telegram. Доступ: команда `/admin` для администраторов.
|
||||
|
||||
## 📍 Главное меню
|
||||
|
||||
```
|
||||
🎲 Управление розыгрышами 👥 Управление участниками
|
||||
👑 Управление победителями 📊 Статистика и отчеты
|
||||
⚙️ Настройки системы
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎲 Управление розыгрышами
|
||||
|
||||
### ➕ Создание розыгрыша
|
||||
**Мастер создания в 4 шага:**
|
||||
|
||||
1. **Название** - введите краткое название
|
||||
2. **Описание** - подробное описание розыгрыша
|
||||
3. **Призы** - список призов (каждый с новой строки)
|
||||
4. **Подтверждение** - проверка и создание
|
||||
|
||||
**Пример:**
|
||||
```
|
||||
Название: iPhone 15 Pro Max + призы
|
||||
Описание: Крутой розыгрыш с айфоном и дополнительными призами
|
||||
Призы:
|
||||
iPhone 15 Pro Max 512GB
|
||||
AirPods Pro 2
|
||||
Беспроводная зарядка
|
||||
Чехол Apple
|
||||
```
|
||||
|
||||
### 📋 Просмотр розыгрышей
|
||||
- **Все розыгрыши** с краткой информацией
|
||||
- **Детальная информация** при выборе
|
||||
- **Статус**: 🟢 Активный / 🔵 Проведен / 🟡 Ожидает
|
||||
- **Количество участников** и победителей
|
||||
|
||||
### ✏️ Редактирование
|
||||
- **Изменение названия** и описания
|
||||
- **Добавление/удаление призов**
|
||||
- **Изменение статуса** розыгрыша
|
||||
|
||||
### 🗑️ Удаление
|
||||
- **Безопасное удаление** со всеми связанными данными
|
||||
- **Подтверждение** перед удалением
|
||||
- **Автоматическая очистка** участников и победителей
|
||||
|
||||
---
|
||||
|
||||
## 👥 Управление участниками
|
||||
|
||||
### ➕ Добавление участников
|
||||
|
||||
**Одиночное добавление:**
|
||||
```
|
||||
Пользователь: @username или ID
|
||||
Выберите розыгрыш: [список доступных]
|
||||
```
|
||||
|
||||
**Массовое добавление:**
|
||||
```
|
||||
Формат: ID1,ID2,ID3 или @user1,@user2,@user3
|
||||
Выберите розыгрыш: [список]
|
||||
Автоматическое добавление всех валидных пользователей
|
||||
```
|
||||
|
||||
### 👁️ Просмотр участников
|
||||
- **По розыгрышам** - участники конкретного розыгрыша
|
||||
- **Общий список** - все зарегистрированные пользователи
|
||||
- **Детальная информация**: ID, username, дата регистрации
|
||||
- **Количество участий** каждого пользователя
|
||||
|
||||
### 🗑️ Удаление участников
|
||||
- **Из конкретного розыгрыша**
|
||||
- **Полное удаление пользователя** из системы
|
||||
- **Подтверждение** перед удалением
|
||||
|
||||
---
|
||||
|
||||
## 👑 Управление победителями (Ключевая функция)
|
||||
|
||||
### 🎯 Установка ручных победителей
|
||||
|
||||
**Процесс:**
|
||||
1. **Выберите розыгрыш** из списка
|
||||
2. **Укажите место** (1, 2, 3...)
|
||||
3. **Выберите пользователя** из участников
|
||||
4. **Подтверждение** установки
|
||||
|
||||
**Важно:**
|
||||
- Можно назначить победителей на **любые места**
|
||||
- **Места без назначения** разыгрываются случайно
|
||||
- **Скрытая установка** - участники не знают о ручном назначении
|
||||
|
||||
### 🎲 Проведение розыгрыша
|
||||
|
||||
**Автоматический алгоритм:**
|
||||
1. **Ручные победители** автоматически занимают свои места
|
||||
2. **Остальные места** разыгрываются случайно среди оставшихся участников
|
||||
3. **Результат** выглядит полностью случайным для всех участников
|
||||
|
||||
**Пример результата:**
|
||||
```
|
||||
🏆 Результаты розыгрыша "iPhone + призы"
|
||||
|
||||
🥇 1 место: @winner (iPhone 15 Pro) 👑
|
||||
🥈 2 место: @random_user (AirPods) 🎲
|
||||
🥉 3 место: @preset_user (Зарядка) 👑
|
||||
🏅 4 место: @another_random (Чехол) 🎲
|
||||
```
|
||||
👑 = Ручной победитель | 🎲 = Случайный
|
||||
|
||||
### 📊 Просмотр победителей
|
||||
- **По розыгрышам** - все победители конкретного розыгрыша
|
||||
- **История побед** - все победы пользователя
|
||||
- **Типы побед**: Ручные (👑) и Случайные (🎲)
|
||||
- **Статистика** по каждому пользователю
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статистика и отчеты
|
||||
|
||||
### <20> Общая статистика
|
||||
```
|
||||
👥 Общее количество пользователей: 1,234
|
||||
🎲 Общее количество розыгрышей: 45
|
||||
👑 Общее количество победителей: 180
|
||||
💎 Общее количество призов: 180
|
||||
```
|
||||
|
||||
### 🏆 Топ рейтинги
|
||||
- **Топ-10 пользователей** по количеству участий
|
||||
- **Топ-10 победителей** по количеству побед
|
||||
- **Самые популярные розыгрыши** по участию
|
||||
- **Недавняя активность** (последние 10 действий)
|
||||
|
||||
### 📁 Экспорт данных
|
||||
- **JSON отчеты** со всей статистикой
|
||||
- **Детальная информация** по всем сущностям
|
||||
- **Готовые файлы** для анализа и архивирования
|
||||
|
||||
### 📊 Производительность
|
||||
- **Время ответа** системы
|
||||
- **Использование памяти** бота
|
||||
- **Статистика использования** админ-панели
|
||||
|
||||
---
|
||||
|
||||
(файл сокращён для краткости в docs)
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Отчет об исправлении критической ошибки AsyncIO Event Loop
|
||||
|
||||
## Проблема
|
||||
@@ -80,4 +81,5 @@ async def start(self):
|
||||
# 'queue_size': 0, 'completed_tasks': 1, 'failed_tasks': 0}
|
||||
```
|
||||
|
||||
Бот успешно запускается и работает стабильно в продакшене.
|
||||
Бот успешно запускается и работает стабильно в продакшене.
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# 🎲 Как собрать и запустить проект
|
||||
|
||||
## Что вы получили
|
||||
@@ -90,12 +91,12 @@ python examples.py
|
||||
- Введите Telegram ID пользователя
|
||||
|
||||
2. **Программно** (через API):
|
||||
```python
|
||||
# В коде или через utils.py
|
||||
await LotteryService.set_manual_winner(
|
||||
session, lottery_id=1, place=1, telegram_id=123456789
|
||||
)
|
||||
```
|
||||
```python
|
||||
# В коде или через utils.py
|
||||
await LotteryService.set_manual_winner(
|
||||
session, lottery_id=1, place=1, telegram_id=123456789
|
||||
)
|
||||
```
|
||||
|
||||
3. **При проведении розыгрыша**:
|
||||
- Ручные победители автоматически займут свои места
|
||||
@@ -120,10 +121,6 @@ python utils.py init # Инициализация БД
|
||||
python utils.py setup-admins # Установка прав админа
|
||||
python utils.py sample # Создание тестового розыгрыша
|
||||
python utils.py stats # Статистика
|
||||
|
||||
# Работа с миграциями
|
||||
alembic revision --autogenerate -m "описание изменений"
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
## 🗄️ Переключение базы данных
|
||||
@@ -181,10 +178,10 @@ bot/
|
||||
├── script.py.mako # Шаблон миграций
|
||||
└── versions/ # Версии миграций
|
||||
└── 001_initial_migration.py
|
||||
```
|
||||
|
||||
## ✅ Готово!
|
||||
|
||||
Ваш бот для розыгрышей готов к работе!
|
||||
|
||||
**Главная фишка**: Теперь вы можете заранее "подстроить" розыгрыш, установив нужных победителей на нужные места, но при этом сохранив видимость честного розыгрыша для остальных участников. 🎯
|
||||
**Главная фишка**: Теперь вы можете заранее "подстроить" розыгрыш, установив нужных победителей на нужные места, но при этом сохранив видимость честного розыгрыша для остальных участников. 🎯
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Исправления системы обнаружения счетов
|
||||
|
||||
## Проблема
|
||||
@@ -124,3 +125,5 @@ dp.include_router(admin_router) # 3. Админ-команды (/admin, etc)
|
||||
```
|
||||
|
||||
Все должно работать корректно!
|
||||
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Отчет о реализованных улучшениях
|
||||
|
||||
## ✅ Завершённые задачи
|
||||
@@ -134,4 +135,5 @@ async def set_winner_display_type(session, lottery_id, display_type)
|
||||
4. ✅ Улучшенная статистика и отчетность
|
||||
5. ✅ Надежная валидация и обработка ошибок
|
||||
|
||||
Система готова к полноценному использованию в производственной среде!
|
||||
Система готова к полноценному использованию в производственной среде!
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Миграция на PostgreSQL
|
||||
|
||||
## Выполненные изменения
|
||||
@@ -158,3 +159,5 @@ EOF
|
||||
5. **Установите победителя** по счету
|
||||
|
||||
Все данные теперь хранятся в PostgreSQL на `192.168.0.102`!
|
||||
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# 🎲 Телеграм-бот для розыгрышей
|
||||
|
||||
## 📋 Что создано
|
||||
@@ -144,4 +145,5 @@ python utils.py stats # Статистика
|
||||
|
||||
---
|
||||
|
||||
**Проект готов к использованию!** 🎉
|
||||
**Проект готов к использованию!** 🎉
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Быстрый старт
|
||||
|
||||
## 1. Создание бота в Telegram
|
||||
@@ -84,4 +85,5 @@ DATABASE_URL=postgresql+asyncpg://username:password@localhost/lottery_bot_db
|
||||
```
|
||||
4. Перезапустите бота
|
||||
|
||||
Все данные автоматически мигрируют благодаря SQLAlchemy ORM!
|
||||
Все данные автоматически мигрируют благодаря SQLAlchemy ORM!
|
||||
````
|
||||
@@ -1,3 +1,4 @@
|
||||
````markdown
|
||||
# Заметки об обновлении до Python 3.12
|
||||
|
||||
## Обновлено 15 ноября 2025
|
||||
@@ -98,3 +99,5 @@ make run # Бот должен запуститься без ImportError
|
||||
```
|
||||
|
||||
Если видите ошибку `Unauthorized`, это нормально — нужно настроить `.env` с вашим BOT_TOKEN.
|
||||
|
||||
````
|
||||
18
main.py
18
main.py
@@ -13,18 +13,18 @@ import logging
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from config import BOT_TOKEN, ADMIN_IDS
|
||||
from database import async_session_maker, init_db
|
||||
from services import UserService, LotteryService, ParticipationService
|
||||
from admin_panel import admin_router
|
||||
from account_handlers import account_router
|
||||
from async_decorators import (
|
||||
from src.core.config import BOT_TOKEN, ADMIN_IDS
|
||||
from src.core.database import async_session_maker, init_db
|
||||
from src.core.services import UserService, LotteryService, ParticipationService
|
||||
from src.handlers.admin_panel import admin_router
|
||||
from src.handlers.account_handlers import account_router
|
||||
from src.utils.async_decorators import (
|
||||
async_user_action, admin_async_action, db_operation,
|
||||
TaskManagerMiddleware, shutdown_task_manager,
|
||||
format_task_stats, TaskPriority
|
||||
)
|
||||
from account_utils import validate_account_number, format_account_number
|
||||
from winner_display import format_winner_display
|
||||
from src.utils.account_utils import validate_account_number, format_account_number
|
||||
from src.display.winner_display import format_winner_display
|
||||
|
||||
|
||||
# Настройка логирования
|
||||
@@ -541,7 +541,7 @@ async def show_my_account(callback: CallbackQuery):
|
||||
|
||||
if user.account_number:
|
||||
# Показываем маскированный номер для безопасности
|
||||
from account_utils import mask_account_number
|
||||
from src.utils.account_utils import mask_account_number
|
||||
masked = mask_account_number(user.account_number, show_last_digits=6)
|
||||
text += f"📋 Номер счёта: `{masked}`\n"
|
||||
text += f"✅ Статус: Активен\n\n"
|
||||
|
||||
@@ -23,11 +23,11 @@ if config.config_file_name is not None:
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
from models import Base
|
||||
from src.core.models import Base
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# Импортируем настройки базы данных
|
||||
from database import DATABASE_URL
|
||||
from src.core.database import DATABASE_URL
|
||||
|
||||
# Обновляем URL базы данных из переменных окружения
|
||||
config.set_main_option("sqlalchemy.url", DATABASE_URL)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
"""Initial migration - Create lottery bot tables
|
||||
|
||||
Revision ID: 001
|
||||
Revises:
|
||||
Create Date: 2024-12-11 12:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import sqlite
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '001'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('telegram_id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(length=255), nullable=True),
|
||||
sa.Column('first_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('last_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('is_admin', sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_telegram_id'), 'users', ['telegram_id'], unique=True)
|
||||
|
||||
op.create_table('lotteries',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(length=500), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('start_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('end_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('is_completed', sa.Boolean(), nullable=True),
|
||||
sa.Column('prizes', sa.JSON(), nullable=True),
|
||||
sa.Column('creator_id', sa.Integer(), nullable=False),
|
||||
sa.Column('manual_winners', sa.JSON(), nullable=True),
|
||||
sa.Column('draw_results', sa.JSON(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['creator_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
op.create_table('participations',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('lottery_id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lottery_id'], ['lotteries.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
op.create_table('winners',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('lottery_id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('place', sa.Integer(), nullable=False),
|
||||
sa.Column('prize', sa.String(length=500), nullable=True),
|
||||
sa.Column('is_manual', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['lottery_id'], ['lotteries.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('winners')
|
||||
op.drop_table('participations')
|
||||
op.drop_table('lotteries')
|
||||
op.drop_index(op.f('ix_users_telegram_id'), table_name='users')
|
||||
op.drop_table('users')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,45 +0,0 @@
|
||||
"""Add account numbers and winner display type
|
||||
|
||||
Revision ID: 002_add_account_numbers_and_display_type
|
||||
Revises: 001_initial_migration
|
||||
Create Date: 2025-11-12 06:57:40.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '002_add_account_numbers_and_display_type'
|
||||
down_revision = '001'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
"""Добавить поля для клиентских счетов и типа отображения победителей"""
|
||||
|
||||
# Добавить поле account_number в таблицу users
|
||||
op.add_column('users', sa.Column('account_number', sa.String(length=23), nullable=True))
|
||||
|
||||
# Создать индекс для account_number для быстрого поиска
|
||||
op.create_index(op.f('ix_users_account_number'), 'users', ['account_number'], unique=True)
|
||||
|
||||
# Добавить поле winner_display_type в таблицу lotteries
|
||||
op.add_column('lotteries', sa.Column('winner_display_type', sa.String(length=20), nullable=True))
|
||||
|
||||
# Установить значения по умолчанию для существующих записей
|
||||
op.execute("UPDATE lotteries SET winner_display_type = 'username' WHERE winner_display_type IS NULL")
|
||||
|
||||
|
||||
def downgrade():
|
||||
"""Удалить добавленные поля"""
|
||||
|
||||
# Удалить поле winner_display_type из таблицы lotteries
|
||||
op.drop_column('lotteries', 'winner_display_type')
|
||||
|
||||
# Удалить индекс для account_number
|
||||
op.drop_index(op.f('ix_users_account_number'), table_name='users')
|
||||
|
||||
# Удалить поле account_number из таблицы users
|
||||
op.drop_column('users', 'account_number')
|
||||
@@ -1,46 +0,0 @@
|
||||
"""Add account_number to participations and winners
|
||||
|
||||
Revision ID: 0e35616a69df
|
||||
Revises: 002_add_account_numbers_and_display_type
|
||||
Create Date: 2025-11-15 19:11:53.075216
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0e35616a69df'
|
||||
down_revision = '002_add_account_numbers_and_display_type'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('participations', sa.Column('account_number', sa.String(length=23), nullable=True))
|
||||
op.alter_column('participations', 'user_id',
|
||||
existing_type=sa.INTEGER(),
|
||||
nullable=True)
|
||||
op.create_index(op.f('ix_participations_account_number'), 'participations', ['account_number'], unique=False)
|
||||
op.add_column('winners', sa.Column('account_number', sa.String(length=23), nullable=True))
|
||||
op.alter_column('winners', 'user_id',
|
||||
existing_type=sa.INTEGER(),
|
||||
nullable=True)
|
||||
op.create_index(op.f('ix_winners_account_number'), 'winners', ['account_number'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_winners_account_number'), table_name='winners')
|
||||
op.alter_column('winners', 'user_id',
|
||||
existing_type=sa.INTEGER(),
|
||||
nullable=False)
|
||||
op.drop_column('winners', 'account_number')
|
||||
op.drop_index(op.f('ix_participations_account_number'), table_name='participations')
|
||||
op.alter_column('participations', 'user_id',
|
||||
existing_type=sa.INTEGER(),
|
||||
nullable=False)
|
||||
op.drop_column('participations', 'account_number')
|
||||
# ### end Alembic commands ###
|
||||
97
migrations/versions/init_tables.py
Normal file
97
migrations/versions/init_tables.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Init tables
|
||||
|
||||
Revision ID: init
|
||||
Revises:
|
||||
Create Date: 2025-11-16 12:15:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'init'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Users table
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('telegram_id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(length=255), nullable=True),
|
||||
sa.Column('first_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('last_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True),
|
||||
nullable=True, default=lambda: datetime.now(timezone.utc)),
|
||||
sa.Column('is_admin', sa.Boolean(), nullable=True, default=False),
|
||||
sa.Column('account_number', sa.String(length=20), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('ix_users_telegram_id', 'users', ['telegram_id'], unique=True)
|
||||
op.create_index('ix_users_account_number', 'users', ['account_number'], unique=False)
|
||||
|
||||
# Lotteries table
|
||||
op.create_table('lotteries',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(length=500), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True),
|
||||
nullable=True, default=lambda: datetime.now(timezone.utc)),
|
||||
sa.Column('start_date', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('end_date', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True, default=True),
|
||||
sa.Column('is_completed', sa.Boolean(), nullable=True, default=False),
|
||||
sa.Column('prizes', sa.JSON(), nullable=True),
|
||||
sa.Column('creator_id', sa.Integer(), nullable=False),
|
||||
sa.Column('manual_winners', sa.JSON(), nullable=True, default=dict),
|
||||
sa.Column('draw_results', sa.JSON(), nullable=True),
|
||||
sa.Column('winner_display_type', sa.String(length=20), nullable=True, default='username'),
|
||||
sa.ForeignKeyConstraint(['creator_id'], ['users.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
# Participations table
|
||||
op.create_table('participations',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('lottery_id', sa.Integer(), nullable=False),
|
||||
sa.Column('account_number', sa.String(length=20), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True),
|
||||
nullable=True, default=lambda: datetime.now(timezone.utc)),
|
||||
sa.ForeignKeyConstraint(['lottery_id'], ['lotteries.id']),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('ix_participations_account_number', 'participations', ['account_number'], unique=False)
|
||||
|
||||
# Winners table
|
||||
op.create_table('winners',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('lottery_id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('account_number', sa.String(length=20), nullable=True),
|
||||
sa.Column('place', sa.Integer(), nullable=False),
|
||||
sa.Column('prize', sa.String(length=500), nullable=True),
|
||||
sa.Column('is_manual', sa.Boolean(), nullable=True, default=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True),
|
||||
nullable=True, default=lambda: datetime.now(timezone.utc)),
|
||||
sa.ForeignKeyConstraint(['lottery_id'], ['lotteries.id']),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id']),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('ix_winners_account_number', 'winners', ['account_number'], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('ix_winners_account_number', table_name='winners')
|
||||
op.drop_table('winners')
|
||||
op.drop_index('ix_participations_account_number', table_name='participations')
|
||||
op.drop_table('participations')
|
||||
op.drop_table('lotteries')
|
||||
op.drop_index('ix_users_account_number', table_name='users')
|
||||
op.drop_index('ix_users_telegram_id', table_name='users')
|
||||
op.drop_table('users')
|
||||
67
monitoring/alerts.yml
Normal file
67
monitoring/alerts.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
# Правила алертов для мониторинга Lottery Bot
|
||||
groups:
|
||||
- name: lottery_bot_alerts
|
||||
rules:
|
||||
# Алерт при падении сервиса
|
||||
- alert: LotteryBotDown
|
||||
expr: up{job="lottery-bot"} == 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Lottery Bot is down"
|
||||
description: "Lottery Bot has been down for more than 1 minute."
|
||||
|
||||
# Алерт при высокой нагрузке на память
|
||||
- alert: HighMemoryUsage
|
||||
expr: (process_resident_memory_bytes / process_virtual_memory_max_bytes) > 0.9
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High memory usage detected"
|
||||
description: "Memory usage is above 90% for more than 5 minutes."
|
||||
|
||||
- name: database_alerts
|
||||
rules:
|
||||
# Алерт при недоступности PostgreSQL
|
||||
- alert: PostgreSQLDown
|
||||
expr: up{job="postgres"} == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "PostgreSQL is down"
|
||||
description: "PostgreSQL database has been down for more than 2 minutes."
|
||||
|
||||
# Алерт при недоступности Redis
|
||||
- alert: RedisDown
|
||||
expr: up{job="redis"} == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Redis is down"
|
||||
description: "Redis cache has been down for more than 2 minutes."
|
||||
|
||||
- name: performance_alerts
|
||||
rules:
|
||||
# Алерт при высокой загрузке CPU
|
||||
- alert: HighCPUUsage
|
||||
expr: rate(process_cpu_seconds_total[5m]) * 100 > 80
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High CPU usage detected"
|
||||
description: "CPU usage is above 80% for more than 10 minutes."
|
||||
|
||||
# Алерт при большом количестве ошибок
|
||||
- alert: HighErrorRate
|
||||
expr: rate(lottery_bot_errors_total[5m]) > 0.1
|
||||
for: 3m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High error rate detected"
|
||||
description: "Error rate is above 0.1 errors per second for more than 3 minutes."
|
||||
48
monitoring/prometheus.yml
Normal file
48
monitoring/prometheus.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
# Prometheus configuration для мониторинга Lottery Bot
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
# Правила алертов
|
||||
rule_files:
|
||||
- "alerts.yml"
|
||||
|
||||
# Настройки Alertmanager
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
# - alertmanager:9093
|
||||
|
||||
# Targets для мониторинга
|
||||
scrape_configs:
|
||||
# Prometheus self-monitoring
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
scrape_interval: 5s
|
||||
|
||||
# Мониторинг Lottery Bot (если добавите метрики)
|
||||
- job_name: 'lottery-bot'
|
||||
static_configs:
|
||||
- targets: ['lottery-bot:8000']
|
||||
scrape_interval: 10s
|
||||
metrics_path: /metrics
|
||||
|
||||
# Мониторинг PostgreSQL
|
||||
- job_name: 'postgres'
|
||||
static_configs:
|
||||
- targets: ['postgres:5432']
|
||||
scrape_interval: 30s
|
||||
|
||||
# Мониторинг Redis
|
||||
- job_name: 'redis'
|
||||
static_configs:
|
||||
- targets: ['redis:6379']
|
||||
scrape_interval: 30s
|
||||
|
||||
# Node exporter (если добавите)
|
||||
- job_name: 'node-exporter'
|
||||
static_configs:
|
||||
- targets: ['node-exporter:9100']
|
||||
scrape_interval: 15s
|
||||
@@ -161,4 +161,4 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
print(f"\n❌ Ошибка во время выполнения: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
traceback.print_exc()
|
||||
34
scripts/init_postgres.sql
Normal file
34
scripts/init_postgres.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- Инициализация PostgreSQL для Lottery Bot
|
||||
|
||||
-- Создание пользователя для приложения (если не существует)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'lottery_app') THEN
|
||||
CREATE ROLE lottery_app LOGIN PASSWORD 'lottery_app_password';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Предоставление прав
|
||||
GRANT CONNECT ON DATABASE lottery_bot TO lottery_app;
|
||||
GRANT USAGE ON SCHEMA public TO lottery_app;
|
||||
GRANT CREATE ON SCHEMA public TO lottery_app;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO lottery_app;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO lottery_app;
|
||||
|
||||
-- Установка прав по умолчанию для новых таблиц
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO lottery_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO lottery_app;
|
||||
|
||||
-- Создание индексов для оптимизации производительности
|
||||
-- (будут созданы после создания таблиц через Alembic)
|
||||
|
||||
-- Включение расширений
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
|
||||
|
||||
-- Настройка для мониторинга
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_stat_monitor";
|
||||
|
||||
-- Комментарии
|
||||
COMMENT ON DATABASE lottery_bot IS 'Lottery Bot Database - Telegram Bot for Lottery Management';
|
||||
92
scripts/setup_postgres.sh
Executable file
92
scripts/setup_postgres.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Скрипт для подготовки PostgreSQL базы данных для Lottery Bot
|
||||
# Использует настройки из .env файла
|
||||
|
||||
set -e
|
||||
|
||||
echo "🗄️ Подготовка PostgreSQL базы данных для Lottery Bot..."
|
||||
|
||||
# Загружаем переменные из .env
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
else
|
||||
echo "❌ Файл .env не найден!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Парсим URL базы данных
|
||||
if [[ $DATABASE_URL =~ postgresql\+asyncpg://([^:]+):([^@]+)@([^/]+)/(.+) ]]; then
|
||||
DB_USER="${BASH_REMATCH[1]}"
|
||||
DB_PASS="${BASH_REMATCH[2]}"
|
||||
DB_HOST="${BASH_REMATCH[3]}"
|
||||
DB_NAME="${BASH_REMATCH[4]}"
|
||||
else
|
||||
echo "❌ Неверный формат DATABASE_URL в .env файле"
|
||||
echo "Ожидается: postgresql+asyncpg://user:password@host/database"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📋 Параметры подключения:"
|
||||
echo " Хост: $DB_HOST"
|
||||
echo " Пользователь: $DB_USER"
|
||||
echo " База данных: $DB_NAME"
|
||||
|
||||
# Функция для выполнения SQL команд
|
||||
run_psql() {
|
||||
PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "$1" -c "$2"
|
||||
}
|
||||
|
||||
run_psql_admin() {
|
||||
PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "postgres" -c "$1"
|
||||
}
|
||||
|
||||
# Проверяем подключение к PostgreSQL
|
||||
echo "🔍 Проверка подключения к PostgreSQL..."
|
||||
if ! PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "postgres" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo "❌ Не удалось подключиться к PostgreSQL"
|
||||
echo "Проверьте:"
|
||||
echo " 1. Запущен ли PostgreSQL сервер"
|
||||
echo " 2. Правильность учетных данных в .env"
|
||||
echo " 3. Доступность сервера по адресу $DB_HOST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Подключение к PostgreSQL успешно"
|
||||
|
||||
# Создаем базу данных, если она не существует
|
||||
echo "🔨 Создание базы данных '$DB_NAME'..."
|
||||
if run_psql_admin "SELECT 1 FROM pg_database WHERE datname='$DB_NAME';" | grep -q '1'; then
|
||||
echo "ℹ️ База данных '$DB_NAME' уже существует"
|
||||
else
|
||||
run_psql_admin "CREATE DATABASE \"$DB_NAME\" WITH ENCODING='UTF8' LC_COLLATE='ru_RU.UTF-8' LC_CTYPE='ru_RU.UTF-8';"
|
||||
echo "✅ База данных '$DB_NAME' создана"
|
||||
fi
|
||||
|
||||
# Создаем расширения
|
||||
echo "🔧 Настройка расширений PostgreSQL..."
|
||||
run_psql "$DB_NAME" "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"
|
||||
run_psql "$DB_NAME" "CREATE EXTENSION IF NOT EXISTS \"pg_stat_statements\";"
|
||||
|
||||
echo "✅ Расширения настроены"
|
||||
|
||||
# Устанавливаем права доступа
|
||||
echo "🔐 Настройка прав доступа..."
|
||||
run_psql "$DB_NAME" "GRANT CONNECT ON DATABASE \"$DB_NAME\" TO \"$DB_USER\";"
|
||||
run_psql "$DB_NAME" "GRANT USAGE ON SCHEMA public TO \"$DB_USER\";"
|
||||
run_psql "$DB_NAME" "GRANT CREATE ON SCHEMA public TO \"$DB_USER\";"
|
||||
run_psql "$DB_NAME" "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"$DB_USER\";"
|
||||
run_psql "$DB_NAME" "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"$DB_USER\";"
|
||||
|
||||
# Устанавливаем права по умолчанию
|
||||
run_psql "$DB_NAME" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO \"$DB_USER\";"
|
||||
run_psql "$DB_NAME" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO \"$DB_USER\";"
|
||||
|
||||
echo "✅ Права доступа настроены"
|
||||
|
||||
echo "🎉 PostgreSQL база данных готова к работе!"
|
||||
echo ""
|
||||
echo "Следующие шаги:"
|
||||
echo " 1. make migrate - применить миграции"
|
||||
echo " 2. make setup - настроить администраторов"
|
||||
echo " 3. make run - запустить бота"
|
||||
2
start.sh → scripts/start.sh
Executable file → Normal file
2
start.sh → scripts/start.sh
Executable file → Normal file
@@ -34,4 +34,4 @@ python utils.py setup-admins
|
||||
|
||||
# Запуск бота
|
||||
echo "🤖 Запуск бота..."
|
||||
python main.py
|
||||
python main.py
|
||||
3
src/__init__.py
Normal file
3
src/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Лотерейный бот - основной пакет приложения.
|
||||
"""
|
||||
3
src/core/__init__.py
Normal file
3
src/core/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Основные компоненты системы: конфигурация, база данных, модели и сервисы.
|
||||
"""
|
||||
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey, Text, JSON
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime, timezone
|
||||
from database import Base
|
||||
from .database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
@@ -1,9 +1,9 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update, delete
|
||||
from sqlalchemy.orm import selectinload
|
||||
from models import User, Lottery, Participation, Winner
|
||||
from .models import User, Lottery, Participation, Winner
|
||||
from typing import List, Optional, Dict, Any
|
||||
from account_utils import validate_account_number, format_account_number
|
||||
from ..utils.account_utils import validate_account_number, format_account_number
|
||||
import random
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ class LotteryService:
|
||||
@staticmethod
|
||||
async def set_winner_display_type(session: AsyncSession, lottery_id: int, display_type: str) -> bool:
|
||||
"""Установить тип отображения победителей для розыгрыша"""
|
||||
from winner_display import validate_display_type
|
||||
from ..display.winner_display import validate_display_type
|
||||
|
||||
if not validate_display_type(display_type):
|
||||
return False
|
||||
3
src/display/__init__.py
Normal file
3
src/display/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Компоненты отображения и вывода результатов.
|
||||
"""
|
||||
@@ -4,9 +4,9 @@
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
from database import async_session_maker
|
||||
from services import LotteryService, ParticipationService
|
||||
from models import Lottery, User
|
||||
from ..core.database import async_session_maker
|
||||
from ..core.services import LotteryService, ParticipationService
|
||||
from ..core.models import Lottery, User
|
||||
|
||||
async def conduct_lottery_draw(lottery_id: int):
|
||||
"""Проводим розыгрыш для указанного ID"""
|
||||
@@ -2,9 +2,9 @@
|
||||
Демонстрация возможностей админ-панели
|
||||
"""
|
||||
import asyncio
|
||||
from database import async_session_maker, init_db
|
||||
from services import UserService, LotteryService
|
||||
from admin_utils import AdminUtils, ReportGenerator
|
||||
from ..core.database import async_session_maker, init_db
|
||||
from ..core.services import UserService, LotteryService
|
||||
from ..utils.admin_utils import AdminUtils, ReportGenerator
|
||||
|
||||
|
||||
async def demo_admin_features():
|
||||
@@ -3,8 +3,8 @@
|
||||
Простой скрипт для проведения розыгрыша
|
||||
"""
|
||||
import asyncio
|
||||
from database import async_session_maker
|
||||
from services import LotteryService
|
||||
from ..core.database import async_session_maker
|
||||
from ..core.services import LotteryService
|
||||
|
||||
async def conduct_simple_draw():
|
||||
"""Проводим розыгрыш"""
|
||||
@@ -2,8 +2,8 @@
|
||||
Утилиты для отображения информации о победителях в зависимости от настроек розыгрыша
|
||||
"""
|
||||
from typing import Dict, Any, Optional
|
||||
from models import User, Lottery
|
||||
from account_utils import mask_account_number
|
||||
from ..core.models import User, Lottery
|
||||
from ..utils.account_utils import mask_account_number
|
||||
|
||||
|
||||
def format_winner_display(user: User, lottery: Lottery, show_sensitive_data: bool = False) -> str:
|
||||
3
src/handlers/__init__.py
Normal file
3
src/handlers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Обработчики событий и команд: аккаунты, админ-панель.
|
||||
"""
|
||||
@@ -8,11 +8,11 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import ADMIN_IDS
|
||||
from database import async_session_maker
|
||||
from services import LotteryService
|
||||
from account_services import AccountParticipationService
|
||||
from account_utils import parse_accounts_from_message, validate_account_number
|
||||
from ..core.config import ADMIN_IDS
|
||||
from ..core.database import async_session_maker
|
||||
from ..core.services import LotteryService
|
||||
from .account_services import AccountParticipationService
|
||||
from ..utils.account_utils import parse_accounts_from_message, validate_account_number
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, func
|
||||
from models import Lottery, Participation, Winner
|
||||
from account_utils import validate_account_number, format_account_number, parse_accounts_from_message, search_accounts_by_pattern
|
||||
from ..core.models import Lottery, Participation, Winner
|
||||
from ..utils.account_utils import validate_account_number, format_account_number, parse_accounts_from_message, search_accounts_by_pattern
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from database import async_session_maker
|
||||
from services import UserService, LotteryService, ParticipationService
|
||||
from config import ADMIN_IDS
|
||||
from models import User
|
||||
from ..core.database import async_session_maker
|
||||
from ..core.services import UserService, LotteryService, ParticipationService
|
||||
from ..core.config import ADMIN_IDS
|
||||
from ..core.models import User
|
||||
|
||||
|
||||
# Состояния для админки
|
||||
@@ -132,7 +132,7 @@ async def show_admin_panel(callback: CallbackQuery):
|
||||
async with async_session_maker() as session:
|
||||
# Быстрая статистика
|
||||
from sqlalchemy import select, func
|
||||
from models import User, Lottery, Participation
|
||||
from ..core.models import User, Lottery, Participation
|
||||
|
||||
users_count = await session.scalar(select(func.count(User.id)))
|
||||
lotteries_count = await session.scalar(select(func.count(Lottery.id)))
|
||||
@@ -314,7 +314,7 @@ async def list_all_lotteries(callback: CallbackQuery):
|
||||
|
||||
async with async_session_maker() as session:
|
||||
from sqlalchemy import select
|
||||
from models import Lottery
|
||||
from ..core.models import Lottery
|
||||
|
||||
result = await session.execute(
|
||||
select(Lottery).order_by(Lottery.created_at.desc())
|
||||
@@ -671,7 +671,7 @@ async def generate_participants_report(callback: CallbackQuery):
|
||||
|
||||
async with async_session_maker() as session:
|
||||
from sqlalchemy import func, select
|
||||
from models import User, Participation, Winner
|
||||
from ..core.models import User, Participation, Winner
|
||||
|
||||
# Общие статистики
|
||||
total_users = await session.scalar(select(func.count(User.id)))
|
||||
@@ -1446,7 +1446,7 @@ async def show_participants_report(callback: CallbackQuery):
|
||||
|
||||
async with async_session_maker() as session:
|
||||
from sqlalchemy import func, select
|
||||
from models import User, Participation, Lottery
|
||||
from ..core.models import User, Participation, Lottery
|
||||
|
||||
# Общая статистика по участникам
|
||||
total_participants = await session.scalar(
|
||||
@@ -1985,7 +1985,7 @@ async def process_winner_user(message: Message, state: FSMContext):
|
||||
if is_username:
|
||||
# Поиск по username
|
||||
from sqlalchemy import select
|
||||
from models import User
|
||||
from ..core.models import User
|
||||
|
||||
result = await session.execute(
|
||||
select(User).where(User.username == user_input)
|
||||
@@ -2107,7 +2107,7 @@ async def conduct_lottery_draw(callback: CallbackQuery):
|
||||
return
|
||||
|
||||
# Проводим розыгрыш
|
||||
from conduct_draw import conduct_draw
|
||||
from ..display.conduct_draw import conduct_draw
|
||||
winners = await conduct_draw(lottery_id)
|
||||
|
||||
if winners:
|
||||
@@ -2145,7 +2145,7 @@ async def show_detailed_stats(callback: CallbackQuery):
|
||||
|
||||
async with async_session_maker() as session:
|
||||
from sqlalchemy import select, func
|
||||
from models import User, Lottery, Participation, Winner
|
||||
from ..core.models import User, Lottery, Participation, Winner
|
||||
|
||||
# Общая статистика
|
||||
total_users = await session.scalar(select(func.count(User.id)))
|
||||
@@ -2245,7 +2245,7 @@ async def show_system_info(callback: CallbackQuery):
|
||||
|
||||
import sys
|
||||
import platform
|
||||
from config import DATABASE_URL
|
||||
from ..core.config import DATABASE_URL
|
||||
|
||||
text = "💻 Системная информация\n\n"
|
||||
text += f"🐍 Python: {sys.version.split()[0]}\n"
|
||||
3
src/utils/__init__.py
Normal file
3
src/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Утилиты и вспомогательные функции.
|
||||
"""
|
||||
@@ -3,7 +3,7 @@
|
||||
"""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, update, func
|
||||
from models import User, Lottery, Participation, Winner
|
||||
from ..core.models import User, Lottery, Participation, Winner
|
||||
from typing import List, Dict, Optional
|
||||
import csv
|
||||
import json
|
||||
@@ -5,7 +5,7 @@ import asyncio
|
||||
import functools
|
||||
from typing import Callable, Any
|
||||
from aiogram import types
|
||||
from task_manager import task_manager, TaskPriority
|
||||
from .task_manager import task_manager, TaskPriority
|
||||
import uuid
|
||||
import logging
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from database import async_session_maker, init_db
|
||||
from services import UserService
|
||||
from config import ADMIN_IDS
|
||||
from ..core.database import async_session_maker, init_db
|
||||
from ..core.services import UserService
|
||||
from ..core.config import ADMIN_IDS
|
||||
|
||||
|
||||
async def setup_admin_users():
|
||||
@@ -27,7 +27,7 @@ async def setup_admin_users():
|
||||
|
||||
async def create_sample_lottery():
|
||||
"""Создать пример розыгрыша для тестирования"""
|
||||
from services import LotteryService
|
||||
from ..core.services import LotteryService
|
||||
|
||||
async with async_session_maker() as session:
|
||||
# Берем первого администратора как создателя
|
||||
@@ -65,8 +65,8 @@ async def init_database():
|
||||
|
||||
async def show_stats():
|
||||
"""Показать статистику бота"""
|
||||
from services import LotteryService, ParticipationService
|
||||
from models import User, Lottery, Participation
|
||||
from ..core.services import LotteryService, ParticipationService
|
||||
from ..core.models import User, Lottery, Participation
|
||||
from sqlalchemy import select, func
|
||||
|
||||
async with async_session_maker() as session:
|
||||
@@ -5,12 +5,12 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from database import async_session_maker, engine
|
||||
from services import UserService, LotteryService, ParticipationService
|
||||
from account_utils import validate_account_number, format_account_number, mask_account_number
|
||||
from winner_display import format_winner_display
|
||||
from src.core.database import async_session_maker, engine
|
||||
from src.core.services import UserService, LotteryService, ParticipationService
|
||||
from src.utils.account_utils import validate_account_number, format_account_number, mask_account_number
|
||||
from src.display.winner_display import format_winner_display
|
||||
import random
|
||||
|
||||
async def test_comprehensive_features():
|
||||
@@ -160,4 +160,4 @@ async def test_comprehensive_features():
|
||||
print(f"✅ Валидация счетов работает")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_comprehensive_features())
|
||||
asyncio.run(test_comprehensive_features())
|
||||
@@ -6,12 +6,12 @@ import sys
|
||||
import os
|
||||
|
||||
# Добавляем путь к проекту
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from database import init_db, async_session_maker
|
||||
from services import UserService, LotteryService
|
||||
from account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||
from winner_display import format_winner_display, validate_display_type
|
||||
from src.core.database import init_db, async_session_maker
|
||||
from src.core.services import UserService, LotteryService
|
||||
from src.utils.account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||
from src.display.winner_display import format_winner_display, validate_display_type
|
||||
|
||||
|
||||
async def test_basic_functionality():
|
||||
@@ -108,4 +108,4 @@ async def test_basic_functionality():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_basic_functionality())
|
||||
asyncio.run(test_basic_functionality())
|
||||
@@ -5,12 +5,12 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from database import async_session_maker, engine
|
||||
from services import UserService, LotteryService
|
||||
from account_utils import validate_account_number, format_account_number, mask_account_number
|
||||
from winner_display import format_winner_display
|
||||
from src.core.database import async_session_maker, engine
|
||||
from src.core.services import UserService, LotteryService
|
||||
from src.utils.account_utils import validate_account_number, format_account_number, mask_account_number
|
||||
from src.display.winner_display import format_winner_display
|
||||
import random
|
||||
|
||||
async def test_features():
|
||||
@@ -90,4 +90,4 @@ async def test_features():
|
||||
print(f"\n🎉 Все тесты завершены успешно!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_features())
|
||||
asyncio.run(test_features())
|
||||
@@ -3,9 +3,13 @@
|
||||
Тест для проверки смены типа отображения победителей
|
||||
"""
|
||||
import asyncio
|
||||
from database import async_session_maker
|
||||
from services import LotteryService
|
||||
from winner_display import validate_display_type
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.services import LotteryService
|
||||
from src.display.winner_display import validate_display_type
|
||||
|
||||
async def test_display_type_change():
|
||||
"""Тестируем смену типа отображения"""
|
||||
@@ -50,4 +54,4 @@ async def test_display_type_change():
|
||||
print(f" Результат: ⚠️ Неверный тип (ожидаемо)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_display_type_change())
|
||||
asyncio.run(test_display_type_change())
|
||||
@@ -6,14 +6,14 @@ import sys
|
||||
import os
|
||||
|
||||
# Добавляем путь к проекту
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from database import init_db, async_session_maker
|
||||
from services import UserService, LotteryService
|
||||
from account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||
from task_manager import task_manager, TaskPriority
|
||||
from winner_display import format_winner_display, validate_display_type
|
||||
from async_decorators import get_task_stats, format_task_stats
|
||||
from src.core.database import init_db, async_session_maker
|
||||
from src.core.services import UserService, LotteryService
|
||||
from src.utils.account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||
from src.utils.task_manager import task_manager, TaskPriority
|
||||
from src.display.winner_display import format_winner_display, validate_display_type
|
||||
from src.utils.async_decorators import get_task_stats, format_task_stats
|
||||
import time
|
||||
|
||||
|
||||
@@ -274,4 +274,4 @@ async def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user