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
|
||||||
28
.env.example
28
.env.example
@@ -1,12 +1,30 @@
|
|||||||
# Переменные окружения для телеграм-бота
|
# Переменные окружения для телеграм-бота
|
||||||
|
|
||||||
|
# Telegram Bot Token (получите у @BotFather)
|
||||||
BOT_TOKEN=your_bot_token_here
|
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
|
ADMIN_IDS=123456789,987654321
|
||||||
|
|
||||||
# Настройки логирования
|
# === ЛОГИРОВАНИЕ ===
|
||||||
|
# Уровень логирования: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
LOG_LEVEL=INFO
|
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 для телеграм-бота розыгрышей
|
# Makefile для телеграм-бота розыгрышей
|
||||||
|
|
||||||
.PHONY: help install setup run test clean
|
.PHONY: help install setup setup-postgres init-db run test clean
|
||||||
|
|
||||||
# По умолчанию показываем справку
|
# По умолчанию показываем справку
|
||||||
help:
|
help:
|
||||||
@@ -8,22 +8,26 @@ help:
|
|||||||
@echo "================================"
|
@echo "================================"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Доступные команды:"
|
@echo "Доступные команды:"
|
||||||
@echo " make install - Установка зависимостей"
|
@echo " make install - Установка зависимостей"
|
||||||
@echo " make setup - Первоначальная настройка"
|
@echo " make setup-postgres- Настройка PostgreSQL БД"
|
||||||
@echo " make run - Запуск бота"
|
@echo " make setup - Первоначальная настройка"
|
||||||
@echo " make test - Запуск тестов и примеров"
|
@echo " make init-db - Инициализация базы данных"
|
||||||
@echo " make migration - Создание миграции"
|
@echo " make run - Запуск бота"
|
||||||
@echo " make migrate - Применение миграций"
|
@echo " make test - Запуск тестов и примеров"
|
||||||
@echo " make sample - Создание тестового розыгрыша"
|
@echo " make migration - Создание миграции"
|
||||||
@echo " make stats - Показать статистику"
|
@echo " make migrate - Применение миграций"
|
||||||
@echo " make demo-admin - Демонстрация админ-панели"
|
@echo " make sample - Создание тестового розыгрыша"
|
||||||
@echo " make test-admin - Тестирование улучшений админки"
|
@echo " make stats - Показать статистику"
|
||||||
|
@echo " make demo-admin - Демонстрация админ-панели"
|
||||||
|
@echo " make test-admin - Тестирование улучшений админки"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Быстрый старт:"
|
@echo "Быстрый старт с PostgreSQL:"
|
||||||
@echo " 1. cp .env.example .env"
|
@echo " 1. cp .env.example .env"
|
||||||
@echo " 2. Отредактируйте .env файл"
|
@echo " 2. Отредактируйте .env файл"
|
||||||
@echo " 3. make setup"
|
@echo " 3. make setup-postgres"
|
||||||
@echo " 4. make run"
|
@echo " 4. make migrate"
|
||||||
|
@echo " 5. make setup"
|
||||||
|
@echo " 6. make run"
|
||||||
|
|
||||||
# Установка зависимостей
|
# Установка зависимостей
|
||||||
install:
|
install:
|
||||||
@@ -31,6 +35,11 @@ install:
|
|||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
. .venv/bin/activate && pip install -r requirements.txt
|
. .venv/bin/activate && pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Настройка PostgreSQL базы данных
|
||||||
|
setup-postgres:
|
||||||
|
@echo "🐘 Настройка PostgreSQL базы данных..."
|
||||||
|
./scripts/setup_postgres.sh
|
||||||
|
|
||||||
# Первоначальная настройка
|
# Первоначальная настройка
|
||||||
setup: install
|
setup: install
|
||||||
@echo "🔧 Настройка проекта..."
|
@echo "🔧 Настройка проекта..."
|
||||||
@@ -38,10 +47,21 @@ setup: install
|
|||||||
echo "❌ Файл .env не найден! Скопируйте .env.example в .env"; \
|
echo "❌ Файл .env не найден! Скопируйте .env.example в .env"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
. .venv/bin/activate && python utils.py init
|
. .venv/bin/activate && python -c "from src.utils.utils import setup_admin_users; import asyncio; asyncio.run(setup_admin_users())"
|
||||||
. .venv/bin/activate && python utils.py setup-admins
|
|
||||||
@echo "✅ Настройка завершена!"
|
@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:
|
run:
|
||||||
@echo "🚀 Запуск бота..."
|
@echo "🚀 Запуск бота..."
|
||||||
@@ -60,27 +80,27 @@ migrate:
|
|||||||
# Тесты и примеры
|
# Тесты и примеры
|
||||||
test:
|
test:
|
||||||
@echo "🧪 Запуск тестов..."
|
@echo "🧪 Запуск тестов..."
|
||||||
. .venv/bin/activate && python examples.py
|
. .venv/bin/activate && python scripts/examples.py
|
||||||
|
|
||||||
# Создание тестового розыгрыша
|
# Создание тестового розыгрыша
|
||||||
sample:
|
sample:
|
||||||
@echo "🎲 Создание тестового розыгрыша..."
|
@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:
|
stats:
|
||||||
@echo "📊 Статистика бота..."
|
@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:
|
demo-admin:
|
||||||
@echo "🎪 Демонстрация возможностей админ-панели..."
|
@echo "🎪 Демонстрация возможностей админ-панели..."
|
||||||
. .venv/bin/activate && python demo_admin.py
|
. .venv/bin/activate && python src/display/demo_admin.py
|
||||||
|
|
||||||
# Тестирование улучшений админки
|
# Тестирование улучшений админки
|
||||||
test-admin:
|
test-admin:
|
||||||
@echo "🧪 Тестирование новых функций админ-панели..."
|
@echo "🧪 Тестирование новых функций админ-панели..."
|
||||||
. .venv/bin/activate && python test_admin_improvements.py
|
. .venv/bin/activate && python tests/test_admin_improvements.py
|
||||||
|
|
||||||
# Очистка
|
# Очистка
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
49
README.md
49
README.md
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# Телеграм-бот для розыгрышей
|
# Телеграм-бот для розыгрышей
|
||||||
|
|
||||||
Телеграм-бот на Python для проведения розыгрышей с возможностью ручной установки победителей.
|
Телеграм-бот на Python для проведения розыгрышей с возможностью ручной установки победителей.
|
||||||
@@ -13,6 +14,9 @@
|
|||||||
- 📈 Детальная статистика и отчеты
|
- 📈 Детальная статистика и отчеты
|
||||||
- 💾 Экспорт данных
|
- 💾 Экспорт данных
|
||||||
- 🧹 Утилиты очистки и обслуживания
|
- 🧹 Утилиты очистки и обслуживания
|
||||||
|
- 🐳 **Docker поддержка** для контейнеризации
|
||||||
|
- 🚀 **CI/CD pipeline** с Drone CI
|
||||||
|
- 📦 **Модульная архитектура** для легкого расширения
|
||||||
|
|
||||||
## Технологии
|
## Технологии
|
||||||
|
|
||||||
@@ -23,11 +27,51 @@
|
|||||||
- **python-dotenv** - управление переменными окружения
|
- **python-dotenv** - управление переменными окружения
|
||||||
- **asyncpg 0.30** - асинхронный драйвер для PostgreSQL
|
- **asyncpg 0.30** - асинхронный драйвер для PostgreSQL
|
||||||
- **aiosqlite 0.20** - асинхронный драйвер для SQLite
|
- **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 # Основной файл бота с интерфейсом
|
├── 📋 main.py # Основной файл бота с интерфейсом
|
||||||
├── 🔧 admin_panel.py # Расширенная админ-панель
|
├── 🔧 admin_panel.py # Расширенная админ-панель
|
||||||
├── 🛠️ admin_utils.py # Утилиты для админки
|
├── 🛠️ admin_utils.py # Утилиты для админки
|
||||||
@@ -250,3 +294,4 @@ CMD ["python", "main.py"]
|
|||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
````
|
||||||
@@ -55,7 +55,8 @@ version_path_separator = os
|
|||||||
# are written from script.py.mako
|
# are written from script.py.mako
|
||||||
# output_encoding = utf-8
|
# 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]
|
||||||
# post_write_hooks defines scripts or Python functions that are run
|
# 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
|
# 🚀 Обновления админ-панели - Changelog
|
||||||
|
|
||||||
## ✨ Добавленные функции (12 ноября 2025)
|
## ✨ Добавленные функции (12 ноября 2025)
|
||||||
@@ -175,3 +176,4 @@ participant_search = State() # Поиск участников
|
|||||||
**Версия**: 2.0
|
**Версия**: 2.0
|
||||||
**Дата**: 12 ноября 2025
|
**Дата**: 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
|
# Отчет об исправлении критической ошибки AsyncIO Event Loop
|
||||||
|
|
||||||
## Проблема
|
## Проблема
|
||||||
@@ -81,3 +82,4 @@ async def start(self):
|
|||||||
```
|
```
|
||||||
|
|
||||||
Бот успешно запускается и работает стабильно в продакшене.
|
Бот успешно запускается и работает стабильно в продакшене.
|
||||||
|
````
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# 🎲 Как собрать и запустить проект
|
# 🎲 Как собрать и запустить проект
|
||||||
|
|
||||||
## Что вы получили
|
## Что вы получили
|
||||||
@@ -90,12 +91,12 @@ python examples.py
|
|||||||
- Введите Telegram ID пользователя
|
- Введите Telegram ID пользователя
|
||||||
|
|
||||||
2. **Программно** (через API):
|
2. **Программно** (через API):
|
||||||
```python
|
```python
|
||||||
# В коде или через utils.py
|
# В коде или через utils.py
|
||||||
await LotteryService.set_manual_winner(
|
await LotteryService.set_manual_winner(
|
||||||
session, lottery_id=1, place=1, telegram_id=123456789
|
session, lottery_id=1, place=1, telegram_id=123456789
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **При проведении розыгрыша**:
|
3. **При проведении розыгрыша**:
|
||||||
- Ручные победители автоматически займут свои места
|
- Ручные победители автоматически займут свои места
|
||||||
@@ -120,10 +121,6 @@ python utils.py init # Инициализация БД
|
|||||||
python utils.py setup-admins # Установка прав админа
|
python utils.py setup-admins # Установка прав админа
|
||||||
python utils.py sample # Создание тестового розыгрыша
|
python utils.py sample # Создание тестового розыгрыша
|
||||||
python utils.py stats # Статистика
|
python utils.py stats # Статистика
|
||||||
|
|
||||||
# Работа с миграциями
|
|
||||||
alembic revision --autogenerate -m "описание изменений"
|
|
||||||
alembic upgrade head
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🗄️ Переключение базы данных
|
## 🗄️ Переключение базы данных
|
||||||
@@ -181,10 +178,10 @@ bot/
|
|||||||
├── script.py.mako # Шаблон миграций
|
├── script.py.mako # Шаблон миграций
|
||||||
└── versions/ # Версии миграций
|
└── versions/ # Версии миграций
|
||||||
└── 001_initial_migration.py
|
└── 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
|
||||||
# Отчет о реализованных улучшениях
|
# Отчет о реализованных улучшениях
|
||||||
|
|
||||||
## ✅ Завершённые задачи
|
## ✅ Завершённые задачи
|
||||||
@@ -135,3 +136,4 @@ async def set_winner_display_type(session, lottery_id, display_type)
|
|||||||
5. ✅ Надежная валидация и обработка ошибок
|
5. ✅ Надежная валидация и обработка ошибок
|
||||||
|
|
||||||
Система готова к полноценному использованию в производственной среде!
|
Система готова к полноценному использованию в производственной среде!
|
||||||
|
````
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# Миграция на PostgreSQL
|
# Миграция на PostgreSQL
|
||||||
|
|
||||||
## Выполненные изменения
|
## Выполненные изменения
|
||||||
@@ -158,3 +159,5 @@ EOF
|
|||||||
5. **Установите победителя** по счету
|
5. **Установите победителя** по счету
|
||||||
|
|
||||||
Все данные теперь хранятся в PostgreSQL на `192.168.0.102`!
|
Все данные теперь хранятся в PostgreSQL на `192.168.0.102`!
|
||||||
|
|
||||||
|
````
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# 🎲 Телеграм-бот для розыгрышей
|
# 🎲 Телеграм-бот для розыгрышей
|
||||||
|
|
||||||
## 📋 Что создано
|
## 📋 Что создано
|
||||||
@@ -145,3 +146,4 @@ python utils.py stats # Статистика
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Проект готов к использованию!** 🎉
|
**Проект готов к использованию!** 🎉
|
||||||
|
````
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# Быстрый старт
|
# Быстрый старт
|
||||||
|
|
||||||
## 1. Создание бота в Telegram
|
## 1. Создание бота в Telegram
|
||||||
@@ -85,3 +86,4 @@ DATABASE_URL=postgresql+asyncpg://username:password@localhost/lottery_bot_db
|
|||||||
4. Перезапустите бота
|
4. Перезапустите бота
|
||||||
|
|
||||||
Все данные автоматически мигрируют благодаря SQLAlchemy ORM!
|
Все данные автоматически мигрируют благодаря SQLAlchemy ORM!
|
||||||
|
````
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
````markdown
|
||||||
# Заметки об обновлении до Python 3.12
|
# Заметки об обновлении до Python 3.12
|
||||||
|
|
||||||
## Обновлено 15 ноября 2025
|
## Обновлено 15 ноября 2025
|
||||||
@@ -98,3 +99,5 @@ make run # Бот должен запуститься без ImportError
|
|||||||
```
|
```
|
||||||
|
|
||||||
Если видите ошибку `Unauthorized`, это нормально — нужно настроить `.env` с вашим BOT_TOKEN.
|
Если видите ошибку `Unauthorized`, это нормально — нужно настроить `.env` с вашим BOT_TOKEN.
|
||||||
|
|
||||||
|
````
|
||||||
18
main.py
18
main.py
@@ -13,18 +13,18 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from config import BOT_TOKEN, ADMIN_IDS
|
from src.core.config import BOT_TOKEN, ADMIN_IDS
|
||||||
from database import async_session_maker, init_db
|
from src.core.database import async_session_maker, init_db
|
||||||
from services import UserService, LotteryService, ParticipationService
|
from src.core.services import UserService, LotteryService, ParticipationService
|
||||||
from admin_panel import admin_router
|
from src.handlers.admin_panel import admin_router
|
||||||
from account_handlers import account_router
|
from src.handlers.account_handlers import account_router
|
||||||
from async_decorators import (
|
from src.utils.async_decorators import (
|
||||||
async_user_action, admin_async_action, db_operation,
|
async_user_action, admin_async_action, db_operation,
|
||||||
TaskManagerMiddleware, shutdown_task_manager,
|
TaskManagerMiddleware, shutdown_task_manager,
|
||||||
format_task_stats, TaskPriority
|
format_task_stats, TaskPriority
|
||||||
)
|
)
|
||||||
from account_utils import validate_account_number, format_account_number
|
from src.utils.account_utils import validate_account_number, format_account_number
|
||||||
from winner_display import format_winner_display
|
from src.display.winner_display import format_winner_display
|
||||||
|
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
@@ -541,7 +541,7 @@ async def show_my_account(callback: CallbackQuery):
|
|||||||
|
|
||||||
if user.account_number:
|
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)
|
masked = mask_account_number(user.account_number, show_last_digits=6)
|
||||||
text += f"📋 Номер счёта: `{masked}`\n"
|
text += f"📋 Номер счёта: `{masked}`\n"
|
||||||
text += f"✅ Статус: Активен\n\n"
|
text += f"✅ Статус: Активен\n\n"
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ if config.config_file_name is not None:
|
|||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
from models import Base
|
from src.core.models import Base
|
||||||
target_metadata = Base.metadata
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
# Импортируем настройки базы данных
|
# Импортируем настройки базы данных
|
||||||
from database import DATABASE_URL
|
from src.core.database import DATABASE_URL
|
||||||
|
|
||||||
# Обновляем URL базы данных из переменных окружения
|
# Обновляем URL базы данных из переменных окружения
|
||||||
config.set_main_option("sqlalchemy.url", DATABASE_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
|
||||||
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 - запустить бота"
|
||||||
0
start.sh → scripts/start.sh
Executable file → Normal file
0
start.sh → scripts/start.sh
Executable file → Normal file
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 import Column, Integer, String, DateTime, Boolean, ForeignKey, Text, JSON
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from database import Base
|
from .database import Base
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, update, delete
|
from sqlalchemy import select, update, delete
|
||||||
from sqlalchemy.orm import selectinload
|
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 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
|
import random
|
||||||
|
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class LotteryService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def set_winner_display_type(session: AsyncSession, lottery_id: int, display_type: str) -> bool:
|
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):
|
if not validate_display_type(display_type):
|
||||||
return False
|
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 asyncio
|
||||||
import json
|
import json
|
||||||
from database import async_session_maker
|
from ..core.database import async_session_maker
|
||||||
from services import LotteryService, ParticipationService
|
from ..core.services import LotteryService, ParticipationService
|
||||||
from models import Lottery, User
|
from ..core.models import Lottery, User
|
||||||
|
|
||||||
async def conduct_lottery_draw(lottery_id: int):
|
async def conduct_lottery_draw(lottery_id: int):
|
||||||
"""Проводим розыгрыш для указанного ID"""
|
"""Проводим розыгрыш для указанного ID"""
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
Демонстрация возможностей админ-панели
|
Демонстрация возможностей админ-панели
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from database import async_session_maker, init_db
|
from ..core.database import async_session_maker, init_db
|
||||||
from services import UserService, LotteryService
|
from ..core.services import UserService, LotteryService
|
||||||
from admin_utils import AdminUtils, ReportGenerator
|
from ..utils.admin_utils import AdminUtils, ReportGenerator
|
||||||
|
|
||||||
|
|
||||||
async def demo_admin_features():
|
async def demo_admin_features():
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
Простой скрипт для проведения розыгрыша
|
Простой скрипт для проведения розыгрыша
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from database import async_session_maker
|
from ..core.database import async_session_maker
|
||||||
from services import LotteryService
|
from ..core.services import LotteryService
|
||||||
|
|
||||||
async def conduct_simple_draw():
|
async def conduct_simple_draw():
|
||||||
"""Проводим розыгрыш"""
|
"""Проводим розыгрыш"""
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
Утилиты для отображения информации о победителях в зависимости от настроек розыгрыша
|
Утилиты для отображения информации о победителях в зависимости от настроек розыгрыша
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from models import User, Lottery
|
from ..core.models import User, Lottery
|
||||||
from account_utils import mask_account_number
|
from ..utils.account_utils import mask_account_number
|
||||||
|
|
||||||
|
|
||||||
def format_winner_display(user: User, lottery: Lottery, show_sensitive_data: bool = False) -> str:
|
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 aiogram.fsm.state import State, StatesGroup
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config import ADMIN_IDS
|
from ..core.config import ADMIN_IDS
|
||||||
from database import async_session_maker
|
from ..core.database import async_session_maker
|
||||||
from services import LotteryService
|
from ..core.services import LotteryService
|
||||||
from account_services import AccountParticipationService
|
from .account_services import AccountParticipationService
|
||||||
from account_utils import parse_accounts_from_message, validate_account_number
|
from ..utils.account_utils import parse_accounts_from_message, validate_account_number
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
"""
|
"""
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, delete, func
|
from sqlalchemy import select, delete, func
|
||||||
from models import Lottery, Participation, Winner
|
from ..core.models import Lottery, Participation, Winner
|
||||||
from account_utils import validate_account_number, format_account_number, parse_accounts_from_message, search_accounts_by_pattern
|
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
|
from typing import List, Optional, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
@@ -12,10 +12,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from database import async_session_maker
|
from ..core.database import async_session_maker
|
||||||
from services import UserService, LotteryService, ParticipationService
|
from ..core.services import UserService, LotteryService, ParticipationService
|
||||||
from config import ADMIN_IDS
|
from ..core.config import ADMIN_IDS
|
||||||
from models import User
|
from ..core.models import User
|
||||||
|
|
||||||
|
|
||||||
# Состояния для админки
|
# Состояния для админки
|
||||||
@@ -132,7 +132,7 @@ async def show_admin_panel(callback: CallbackQuery):
|
|||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
# Быстрая статистика
|
# Быстрая статистика
|
||||||
from sqlalchemy import select, func
|
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)))
|
users_count = await session.scalar(select(func.count(User.id)))
|
||||||
lotteries_count = await session.scalar(select(func.count(Lottery.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:
|
async with async_session_maker() as session:
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from models import Lottery
|
from ..core.models import Lottery
|
||||||
|
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
select(Lottery).order_by(Lottery.created_at.desc())
|
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:
|
async with async_session_maker() as session:
|
||||||
from sqlalchemy import func, select
|
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)))
|
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:
|
async with async_session_maker() as session:
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from models import User, Participation, Lottery
|
from ..core.models import User, Participation, Lottery
|
||||||
|
|
||||||
# Общая статистика по участникам
|
# Общая статистика по участникам
|
||||||
total_participants = await session.scalar(
|
total_participants = await session.scalar(
|
||||||
@@ -1985,7 +1985,7 @@ async def process_winner_user(message: Message, state: FSMContext):
|
|||||||
if is_username:
|
if is_username:
|
||||||
# Поиск по username
|
# Поиск по username
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from models import User
|
from ..core.models import User
|
||||||
|
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
select(User).where(User.username == user_input)
|
select(User).where(User.username == user_input)
|
||||||
@@ -2107,7 +2107,7 @@ async def conduct_lottery_draw(callback: CallbackQuery):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Проводим розыгрыш
|
# Проводим розыгрыш
|
||||||
from conduct_draw import conduct_draw
|
from ..display.conduct_draw import conduct_draw
|
||||||
winners = await conduct_draw(lottery_id)
|
winners = await conduct_draw(lottery_id)
|
||||||
|
|
||||||
if winners:
|
if winners:
|
||||||
@@ -2145,7 +2145,7 @@ async def show_detailed_stats(callback: CallbackQuery):
|
|||||||
|
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
from sqlalchemy import select, func
|
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)))
|
total_users = await session.scalar(select(func.count(User.id)))
|
||||||
@@ -2245,7 +2245,7 @@ async def show_system_info(callback: CallbackQuery):
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
from config import DATABASE_URL
|
from ..core.config import DATABASE_URL
|
||||||
|
|
||||||
text = "💻 Системная информация\n\n"
|
text = "💻 Системная информация\n\n"
|
||||||
text += f"🐍 Python: {sys.version.split()[0]}\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.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, delete, update, func
|
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
|
from typing import List, Dict, Optional
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
@@ -5,7 +5,7 @@ import asyncio
|
|||||||
import functools
|
import functools
|
||||||
from typing import Callable, Any
|
from typing import Callable, Any
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from task_manager import task_manager, TaskPriority
|
from .task_manager import task_manager, TaskPriority
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from database import async_session_maker, init_db
|
from ..core.database import async_session_maker, init_db
|
||||||
from services import UserService
|
from ..core.services import UserService
|
||||||
from config import ADMIN_IDS
|
from ..core.config import ADMIN_IDS
|
||||||
|
|
||||||
|
|
||||||
async def setup_admin_users():
|
async def setup_admin_users():
|
||||||
@@ -27,7 +27,7 @@ async def setup_admin_users():
|
|||||||
|
|
||||||
async def create_sample_lottery():
|
async def create_sample_lottery():
|
||||||
"""Создать пример розыгрыша для тестирования"""
|
"""Создать пример розыгрыша для тестирования"""
|
||||||
from services import LotteryService
|
from ..core.services import LotteryService
|
||||||
|
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
# Берем первого администратора как создателя
|
# Берем первого администратора как создателя
|
||||||
@@ -65,8 +65,8 @@ async def init_database():
|
|||||||
|
|
||||||
async def show_stats():
|
async def show_stats():
|
||||||
"""Показать статистику бота"""
|
"""Показать статистику бота"""
|
||||||
from services import LotteryService, ParticipationService
|
from ..core.services import LotteryService, ParticipationService
|
||||||
from models import User, Lottery, Participation
|
from ..core.models import User, Lottery, Participation
|
||||||
from sqlalchemy import select, func
|
from sqlalchemy import select, func
|
||||||
|
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import os
|
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 src.core.database import async_session_maker, engine
|
||||||
from services import UserService, LotteryService, ParticipationService
|
from src.core.services import UserService, LotteryService, ParticipationService
|
||||||
from account_utils import validate_account_number, format_account_number, mask_account_number
|
from src.utils.account_utils import validate_account_number, format_account_number, mask_account_number
|
||||||
from winner_display import format_winner_display
|
from src.display.winner_display import format_winner_display
|
||||||
import random
|
import random
|
||||||
|
|
||||||
async def test_comprehensive_features():
|
async def test_comprehensive_features():
|
||||||
@@ -6,12 +6,12 @@ import sys
|
|||||||
import os
|
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 src.core.database import init_db, async_session_maker
|
||||||
from services import UserService, LotteryService
|
from src.core.services import UserService, LotteryService
|
||||||
from account_utils import generate_account_number, validate_account_number, mask_account_number
|
from src.utils.account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||||
from winner_display import format_winner_display, validate_display_type
|
from src.display.winner_display import format_winner_display, validate_display_type
|
||||||
|
|
||||||
|
|
||||||
async def test_basic_functionality():
|
async def test_basic_functionality():
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
import os
|
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 src.core.database import async_session_maker, engine
|
||||||
from services import UserService, LotteryService
|
from src.core.services import UserService, LotteryService
|
||||||
from account_utils import validate_account_number, format_account_number, mask_account_number
|
from src.utils.account_utils import validate_account_number, format_account_number, mask_account_number
|
||||||
from winner_display import format_winner_display
|
from src.display.winner_display import format_winner_display
|
||||||
import random
|
import random
|
||||||
|
|
||||||
async def test_features():
|
async def test_features():
|
||||||
@@ -3,9 +3,13 @@
|
|||||||
Тест для проверки смены типа отображения победителей
|
Тест для проверки смены типа отображения победителей
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from database import async_session_maker
|
import sys
|
||||||
from services import LotteryService
|
import os
|
||||||
from winner_display import validate_display_type
|
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():
|
async def test_display_type_change():
|
||||||
"""Тестируем смену типа отображения"""
|
"""Тестируем смену типа отображения"""
|
||||||
@@ -6,14 +6,14 @@ import sys
|
|||||||
import os
|
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 src.core.database import init_db, async_session_maker
|
||||||
from services import UserService, LotteryService
|
from src.core.services import UserService, LotteryService
|
||||||
from account_utils import generate_account_number, validate_account_number, mask_account_number
|
from src.utils.account_utils import generate_account_number, validate_account_number, mask_account_number
|
||||||
from task_manager import task_manager, TaskPriority
|
from src.utils.task_manager import task_manager, TaskPriority
|
||||||
from winner_display import format_winner_display, validate_display_type
|
from src.display.winner_display import format_winner_display, validate_display_type
|
||||||
from async_decorators import get_task_stats, format_task_stats
|
from src.utils.async_decorators import get_task_stats, format_task_stats
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user