This commit is contained in:
2025-11-16 12:36:02 +09:00
parent 3a25e6a4cb
commit eb3f3807fd
61 changed files with 1438 additions and 1139 deletions

197
scripts/db_setup.py Normal file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/env python3
"""
Скрипт для очистки и инициализации БД с тестовыми данными
"""
import asyncio
import random
from database import async_session_maker, Base, engine
from models import User, Lottery, Participation, Winner
from services import LotteryService, UserService, ParticipationService
from datetime import datetime, timedelta
from sqlalchemy import delete, update, select, func
async def clear_database():
"""Очистка всех таблиц"""
print("🗑️ Очистка базы данных...")
async with async_session_maker() as session:
# Удаляем все данные в правильном порядке (учитывая внешние ключи)
await session.execute(delete(Winner))
await session.execute(delete(Participation))
await session.execute(delete(Lottery))
await session.execute(delete(User))
await session.commit()
print("✅ База данных очищена")
def generate_account_numbers(user_id: int, count: int = 10) -> list:
"""Генерация номеров счетов для пользователя"""
base = user_id * 1000
return [f"{base + i:06d}" for i in range(1, count + 1)]
async def create_test_users():
"""Создание тестовых пользователей"""
print("👥 Создание тестовых пользователей...")
users_data = [
{"telegram_id": 111111111, "username": "alice_winner", "first_name": "Alice", "last_name": "Johnson"},
{"telegram_id": 222222222, "username": "bob_player", "first_name": "Bob", "last_name": "Smith"},
{"telegram_id": 333333333, "username": "charlie_test", "first_name": "Charlie", "last_name": "Brown"},
{"telegram_id": 444444444, "username": "diana_luck", "first_name": "Diana", "last_name": "Wilson"},
{"telegram_id": 555555555, "username": "eve_gamer", "first_name": "Eve", "last_name": "Davis"},
{"telegram_id": 666666666, "username": "frank_player", "first_name": "Frank", "last_name": "Miller"},
{"telegram_id": 777777777, "username": "grace_lucky", "first_name": "Grace", "last_name": "Taylor"},
{"telegram_id": 888888888, "username": "henry_bet", "first_name": "Henry", "last_name": "Anderson"},
{"telegram_id": 999999999, "username": "iris_game", "first_name": "Iris", "last_name": "Thomas"},
{"telegram_id": 101010101, "username": "jack_chance", "first_name": "Jack", "last_name": "Jackson"},
]
created_users = []
async with async_session_maker() as session:
for i, user_data in enumerate(users_data, 1):
# Создаем пользователя
user = await UserService.get_or_create_user(
session=session,
telegram_id=user_data["telegram_id"],
username=user_data["username"],
first_name=user_data["first_name"],
last_name=user_data.get("last_name", None)
)
# Генерируем и присваиваем номера счетов
account_numbers = generate_account_numbers(i, 10)
# Обновляем счета
await session.execute(
update(User)
.where(User.id == user.id)
.values(account_number=",".join(account_numbers))
)
await session.commit()
created_users.append((user, account_numbers))
print(f"✅ Создан пользователь {user.first_name} ({user.username}) с {len(account_numbers)} счетами")
return created_users
async def create_test_lotteries(users_data):
"""Создание тестовых розыгрышей"""
print("🎲 Создание тестовых розыгрышей...")
async with async_session_maker() as session:
# Первый розыгрыш - активный
lottery1 = await LotteryService.create_lottery(
session=session,
title="🎯 Супер Розыгрыш #1",
description="Розыгрыш крутых призов! Участвуют все желающие.",
prizes=[
"iPhone 15 Pro - Новейший флагман Apple",
"AirPods Pro - Беспроводные наушники премиум класса",
"Apple Watch - Умные часы последней модели"
],
creator_id=users_data[0][0].id
)
# Второй розыгрыш - тоже активный
lottery2 = await LotteryService.create_lottery(
session=session,
title="🚀 Мега Розыгрыш #2",
description="Еще больше призов! Еще больше возможностей!",
prizes=[
"MacBook Pro - Мощный ноутбук для профессионалов",
"iPad Air - Планшет для творчества и работы",
"Денежный приз 50,000 рублей"
],
creator_id=users_data[0][0].id
)
await session.commit()
print(f"✅ Создан розыгрыш: {lottery1.title} (ID: {lottery1.id})")
print(f"✅ Создан розыгрыш: {lottery2.title} (ID: {lottery2.id})")
return [lottery1, lottery2]
async def populate_participants(users_data, lotteries):
"""Заполнение участников розыгрышей"""
print("🎫 Добавление участников в розыгрыши...")
async with async_session_maker() as session:
# Добавляем всех пользователей в первый розыгрыш
lottery1 = lotteries[0]
for user, account_numbers in users_data:
# Добавляем каждого пользователя как участника
success = await ParticipationService.add_participant(
session=session,
lottery_id=lottery1.id,
user_id=user.id
)
if success:
print(f"✅ Пользователь {user.first_name}: добавлен в розыгрыш №1")
else:
print(f"⚠️ Пользователь {user.first_name}: уже участвует в розыгрыше №1")
# Добавляем первых 5 пользователей во второй розыгрыш
lottery2 = lotteries[1]
for user, account_numbers in users_data[:5]: # Первые 5 пользователей
success = await ParticipationService.add_participant(
session=session,
lottery_id=lottery2.id,
user_id=user.id
)
if success:
print(f"✅ Пользователь {user.first_name}: добавлен в розыгрыш №2")
else:
print(f"⚠️ Пользователь {user.first_name}: уже участвует в розыгрыше №2")
async def show_statistics():
"""Показ статистики созданных данных"""
print("\n📊 Статистика созданных данных:")
async with async_session_maker() as session:
# Пользователи
users = await UserService.get_all_users(session)
print(f"👥 Пользователей: {len(users)}")
# Розыгрыши
lotteries = await LotteryService.get_active_lotteries(session)
print(f"🎲 Активных розыгрышей: {len(lotteries)}")
# Участия
for lottery in lotteries:
# Простой подсчет через SQL
result = await session.execute(
select(func.count(Participation.id))
.where(Participation.lottery_id == lottery.id)
)
participation_count = result.scalar()
print(f"🎫 Розыгрыш '{lottery.title}': {participation_count} участий")
print("\n✅ Тестовые данные успешно созданы!")
async def main():
"""Главная функция"""
print("🚀 Инициализация тестовой базы данных")
print("=" * 50)
# Очищаем БД
await clear_database()
# Создаем тестовых пользователей
users_data = await create_test_users()
# Создаем розыгрыши
lotteries = await create_test_lotteries(users_data)
# Заполняем участников
await populate_participants(users_data, lotteries)
# Показываем статистику
await show_statistics()
print("\n🎉 Инициализация завершена!")
if __name__ == "__main__":
asyncio.run(main())

164
scripts/examples.py Normal file
View File

@@ -0,0 +1,164 @@
"""
Примеры использования API сервисов для тестирования
"""
import asyncio
from database import async_session_maker, init_db
from services import UserService, LotteryService, ParticipationService
async def example_usage():
"""Пример использования всех основных функций бота"""
print("🔄 Инициализация базы данных...")
await init_db()
async with async_session_maker() as session:
print("\n👤 Создание пользователей...")
# Создаем администратора
admin = await UserService.get_or_create_user(
session,
telegram_id=123456789,
username="admin_user",
first_name="Администратор",
last_name="Бота"
)
await UserService.set_admin(session, 123456789, True)
print(f"✅ Создан админ: {admin.first_name} (@{admin.username})")
# Создаем обычных пользователей
users = []
for i in range(1, 6):
user = await UserService.get_or_create_user(
session,
telegram_id=100000000 + i,
username=f"user{i}",
first_name=f"Пользователь{i}",
last_name="Тестовый"
)
users.append(user)
print(f"✅ Создан пользователь: {user.first_name}")
print(f"\n🎲 Создание розыгрыша...")
# Создаем розыгрыш
lottery = await LotteryService.create_lottery(
session,
title="🎉 Новогодний розыгрыш 2025",
description="Грандиозный новогодний розыгрыш с крутыми призами!",
prizes=[
"🥇 iPhone 15 Pro Max",
"🥈 MacBook Air M2",
"🥉 AirPods Pro",
"🏆 PlayStation 5",
"🎁 Подарочный сертификат 10,000₽"
],
creator_id=admin.id
)
print(f"✅ Создан розыгрыш: {lottery.title}")
print(f"\n🎫 Регистрация участников...")
# Добавляем участников
for user in users:
success = await LotteryService.add_participant(session, lottery.id, user.id)
if success:
print(f"{user.first_name} присоединился к розыгрышу")
print(f"\n👑 Установка ручного победителя...")
# Устанавливаем ручного победителя на 1 место
manual_winner = users[0] # Первый пользователь
success = await LotteryService.set_manual_winner(
session, lottery.id, 1, manual_winner.telegram_id
)
if success:
print(f"{manual_winner.first_name} установлен как победитель 1 места")
# Устанавливаем еще одного ручного победителя на 3 место
manual_winner2 = users[2] # Третий пользователь
success = await LotteryService.set_manual_winner(
session, lottery.id, 3, manual_winner2.telegram_id
)
if success:
print(f"{manual_winner2.first_name} установлен как победитель 3 места")
print(f"\n📊 Статистика перед розыгрышем...")
participants_count = await ParticipationService.get_participants_count(
session, lottery.id
)
print(f"👥 Участников в розыгрыше: {participants_count}")
print(f"\n🎲 Проведение розыгрыша...")
# Проводим розыгрыш
results = await LotteryService.conduct_draw(session, lottery.id)
print(f"\n🏆 Результаты розыгрыша:")
print(f"🎯 Розыгрыш: {lottery.title}")
print("=" * 50)
for place, winner_info in results.items():
user = winner_info['user']
prize = winner_info['prize']
is_manual = winner_info['is_manual']
manual_indicator = "👑 (установлен вручную)" if is_manual else "🎲 (случайный)"
print(f"{place}. {user.first_name} (@{user.username}) {manual_indicator}")
print(f" 🎁 {prize}")
print()
print("=" * 50)
print("✅ Розыгрыш завершен успешно!")
print(f"\n📈 Финальная статистика...")
# Получаем победителей из базы данных
winners = await LotteryService.get_winners(session, lottery.id)
print(f"🏆 Сохранено победителей в базе: {len(winners)}")
# Проверяем участия пользователей
for user in users[:2]: # Проверяем первых двух
participations = await ParticipationService.get_user_participations(
session, user.id
)
print(f"📝 {user.first_name} участвует в {len(participations)} розыгрышах")
async def test_edge_cases():
"""Тестирование граничных случаев"""
print("\n🧪 Тестирование граничных случаев...")
async with async_session_maker() as session:
# Попытка добавить участника дважды
user = await UserService.get_user_by_telegram_id(session, 100000001)
lottery = (await LotteryService.get_active_lotteries(session))[0]
success = await LotteryService.add_participant(session, lottery.id, user.id)
print(f"Повторное добавление участника: {'❌ Заблокировано' if not success else '⚠️ Разрешено'}")
# Попытка установить ручного победителя для несуществующего пользователя
success = await LotteryService.set_manual_winner(session, lottery.id, 10, 999999999)
print(f"Установка несуществующего победителя: {'❌ Заблокировано' if not success else '⚠️ Разрешено'}")
# Попытка провести розыгрыш завершенной лотереи
results = await LotteryService.conduct_draw(session, lottery.id)
print(f"Повторный розыгрыш: {'❌ Заблокирован' if not results else '⚠️ Разрешен'}")
if __name__ == "__main__":
print("🚀 Запуск примера использования бота для розыгрышей")
print("=" * 60)
try:
asyncio.run(example_usage())
asyncio.run(test_edge_cases())
print("\n" + "=" * 60)
print("Все тесты выполнены успешно!")
print("🎉 Бот готов к использованию!")
except Exception as e:
print(f"\n❌ Ошибка во время выполнения: {e}")
import traceback
traceback.print_exc()

34
scripts/init_postgres.sql Normal file
View 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
View 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 - запустить бота"

37
scripts/start.sh Normal file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
# Скрипт запуска бота для проедения розыгрышей
echo "🚀 Запуск телеграм-бота для розыгрышей"
# Проверка виртуального окружения
if [[ "$VIRTUAL_ENV" == "" ]]; then
echo "⚠️ Рекомендуется использовать виртуальное окружение"
echo "Создайте его командой: python -m venv venv"
echo "Активируйте: source venv/bin/activate"
echo ""
fi
# Проверка файла .env
if [ ! -f .env ]; then
echo "❌ Файл .env не найден!"
echo "Скопируйте .env.example в .env и заполните переменные"
echo "cp .env.example .env"
exit 1
fi
# Проверка зависимостей
echo "📦 Проверка зависимостей..."
pip install -r requirements.txt
# Инициализация базы данных
echo "🔄 Инициализация базы данных..."
python utils.py init
# Установка прав администратора
echo "👑 Настройка администраторов..."
python utils.py setup-admins
# Запуск бота
echo "🤖 Запуск бота..."
python main.py