Files
pyproject2/handlers/auth.py
2025-12-25 15:25:46 +03:00

341 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)