initial commit
This commit is contained in:
340
handlers/auth.py
Normal file
340
handlers/auth.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user