initial commit

This commit is contained in:
2025-12-25 15:25:46 +03:00
commit 58827ac124
20 changed files with 1854 additions and 0 deletions

340
handlers/auth.py Normal file
View File

@@ -0,0 +1,340 @@
from aiogram import Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from database.models import User
from database.connection import async_session_maker
from utils.states import RegistrationStates, LoginStates
from keyboards.inline import get_auth_menu, get_main_menu, get_back_to_menu_keyboard
import bcrypt
import logging
import re
router = Router()
logger = logging.getLogger(__name__)
@router.message(CommandStart())
async def cmd_start(message: Message, state: FSMContext):
"""Обработчик команды /start"""
await state.clear()
telegram_id = message.from_user.id
try:
async with async_session_maker() as session:
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = result.scalar_one_or_none()
if user:
# Пользователь авторизован
await message.answer(
f"👋 Добро пожаловать, {user.first_name or user.username}!\n\n"
"📚 Выберите действие:",
reply_markup=get_main_menu()
)
else:
# Пользователь не авторизован
await message.answer(
"👋 Добро пожаловать в бот 'Что почитать'!\n\n"
"📚 Здесь вы можете просматривать книги с описаниями, обложками и рейтингами.\n\n"
"Пожалуйста, войдите или зарегистрируйтесь:",
reply_markup=get_auth_menu()
)
except Exception as e:
logger.error(f"Ошибка в cmd_start: {e}")
await message.answer("❌ Произошла ошибка. Попробуйте позже.")
@router.callback_query(F.data == "main_menu")
async def show_main_menu(callback: CallbackQuery, state: FSMContext):
"""Показать главное меню"""
await state.clear()
telegram_id = callback.from_user.id
try:
async with async_session_maker() as session:
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = result.scalar_one_or_none()
if user:
await callback.message.edit_text(
f"👋 Добро пожаловать, {user.first_name or user.username}!\n\n"
"📚 Выберите действие:",
reply_markup=get_main_menu()
)
else:
await callback.message.edit_text(
"👋 Добро пожаловать в бот 'Что почитать'!\n\n"
"Пожалуйста, войдите или зарегистрируйтесь:",
reply_markup=get_auth_menu()
)
await callback.answer()
except Exception as e:
logger.error(f"Ошибка в show_main_menu: {e}")
await callback.answer("❌ Ошибка", show_alert=True)
# === РЕГИСТРАЦИЯ ===
@router.callback_query(F.data == "register")
async def start_registration(callback: CallbackQuery, state: FSMContext):
"""Начало регистрации"""
await state.set_state(RegistrationStates.waiting_email)
await callback.message.edit_text(
"📝 Регистрация\n\n"
"Введите ваш email:"
)
await callback.answer()
@router.message(RegistrationStates.waiting_email)
async def process_registration_email(message: Message, state: FSMContext):
"""Обработка email при регистрации"""
email = message.text.strip().lower()
# Валидация email
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
await message.answer("❌ Неверный формат email. Попробуйте снова:")
return
try:
async with async_session_maker() as session:
# Проверка уникальности email
result = await session.execute(
select(User).where(User.email == email)
)
existing_user = result.scalar_one_or_none()
if existing_user:
await message.answer(
"❌ Этот email уже зарегистрирован.\n\n"
"Используйте другой email или войдите в систему."
)
await state.clear()
return
await state.update_data(email=email)
await state.set_state(RegistrationStates.waiting_username)
await message.answer("✅ Email принят.\n\nТеперь введите желаемое имя пользователя (username):")
except Exception as e:
logger.error(f"Ошибка при проверке email: {e}")
await message.answer("❌ Произошла ошибка. Попробуйте снова.")
await state.clear()
@router.message(RegistrationStates.waiting_username)
async def process_registration_username(message: Message, state: FSMContext):
"""Обработка username при регистрации"""
username = message.text.strip()
# Валидация username
if len(username) < 3 or len(username) > 150:
await message.answer("❌ Имя пользователя должно быть от 3 до 150 символов. Попробуйте снова:")
return
if not re.match(r'^[a-zA-Z0-9_]+$', username):
await message.answer("❌ Имя пользователя может содержать только буквы, цифры и подчеркивание. Попробуйте снова:")
return
try:
async with async_session_maker() as session:
# Проверка уникальности username
result = await session.execute(
select(User).where(User.username == username)
)
existing_user = result.scalar_one_or_none()
if existing_user:
await message.answer("❌ Это имя пользователя уже занято. Попробуйте другое:")
return
await state.update_data(username=username)
await state.set_state(RegistrationStates.waiting_password)
await message.answer(
"✅ Имя пользователя принято.\n\n"
"Теперь введите пароль (минимум 8 символов):"
)
except Exception as e:
logger.error(f"Ошибка при проверке username: {e}")
await message.answer("❌ Произошла ошибка. Попробуйте снова.")
await state.clear()
@router.message(RegistrationStates.waiting_password)
async def process_registration_password(message: Message, state: FSMContext):
"""Обработка пароля и завершение регистрации"""
password = message.text.strip()
# Валидация пароля
if len(password) < 8:
await message.answer("❌ Пароль должен содержать минимум 8 символов. Попробуйте снова:")
return
try:
# Удаляем сообщение с паролем из чата
await message.delete()
except:
pass
try:
data = await state.get_data()
email = data['email']
username = data['username']
# Хешируем пароль
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
async with async_session_maker() as session:
# Создаем нового пользователя
new_user = User(
email=email,
username=username,
password=hashed_password,
telegram_id=message.from_user.id,
first_name=message.from_user.first_name or "",
last_name=message.from_user.last_name or "",
is_active=True
)
session.add(new_user)
await session.commit()
await message.answer(
f"✅ Регистрация успешно завершена!\n\n"
f"👤 Ваш username: {username}\n"
f"📧 Email: {email}\n\n"
"Добро пожаловать в бот 'Что почитать'!",
reply_markup=get_main_menu()
)
await state.clear()
except Exception as e:
logger.error(f"Ошибка при создании пользователя: {e}")
await message.answer("❌ Произошла ошибка при регистрации. Попробуйте снова.")
await state.clear()
# === ВХОД ===
@router.callback_query(F.data == "login")
async def start_login(callback: CallbackQuery, state: FSMContext):
"""Начало входа"""
await state.set_state(LoginStates.waiting_email)
await callback.message.edit_text(
"🔐 Вход в систему\n\n"
"Введите ваш email:"
)
await callback.answer()
@router.message(LoginStates.waiting_email)
async def process_login_email(message: Message, state: FSMContext):
"""Обработка email при входе"""
email = message.text.strip().lower()
await state.update_data(email=email)
await state.set_state(LoginStates.waiting_password)
await message.answer("Теперь введите ваш пароль:")
@router.message(LoginStates.waiting_password)
async def process_login_password(message: Message, state: FSMContext):
"""Обработка пароля и завершение входа"""
password = message.text.strip()
try:
# Удаляем сообщение с паролем
await message.delete()
except:
pass
try:
data = await state.get_data()
email = data['email']
async with async_session_maker() as session:
result = await session.execute(
select(User).where(User.email == email)
)
user = result.scalar_one_or_none()
if not user:
await message.answer("❌ Пользователь с таким email не найден.")
await state.clear()
return
# Проверяем пароль
if not bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')):
await message.answer("❌ Неверный пароль.")
await state.clear()
return
# Проверяем, не привязан ли уже другой Telegram аккаунт
if user.telegram_id and user.telegram_id != message.from_user.id:
await message.answer("❌ Этот аккаунт уже привязан к другому Telegram профилю.")
await state.clear()
return
# Обновляем telegram_id
user.telegram_id = message.from_user.id
await session.commit()
await message.answer(
f"✅ Вход выполнен успешно!\n\n"
f"👋 Добро пожаловать, {user.first_name or user.username}!",
reply_markup=get_main_menu()
)
await state.clear()
except Exception as e:
logger.error(f"Ошибка при входе: {e}")
await message.answer("❌ Произошла ошибка при входе. Попробуйте снова.")
await state.clear()
# === ПРОФИЛЬ ===
@router.callback_query(F.data == "profile")
async def show_profile(callback: CallbackQuery):
"""Показать профиль пользователя"""
telegram_id = callback.from_user.id
try:
async with async_session_maker() as session:
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = result.scalar_one_or_none()
if not user:
await callback.answer("⛔ Пользователь не найден", show_alert=True)
return
profile_text = (
f"👤 Ваш профиль\n\n"
f"🆔 Username: {user.username}\n"
f"📧 Email: {user.email}\n"
f"👤 Имя: {user.first_name or 'Не указано'}\n"
f"📅 Дата регистрации: {user.date_joined.strftime('%d.%m.%Y')}"
)
await callback.message.edit_text(
profile_text,
reply_markup=get_back_to_menu_keyboard()
)
await callback.answer()
except Exception as e:
logger.error(f"Ошибка в show_profile: {e}")
await callback.answer("❌ Ошибка", show_alert=True)