add verification logic

This commit is contained in:
oleg.vodyanov91@gmail.com 2025-06-08 22:32:01 +03:00
parent 414729c41f
commit c97cf5e0d8
6 changed files with 242 additions and 6 deletions

View File

@ -9,3 +9,13 @@ class ConfirmationMessage(str, Enum):
class Acknowledgement(str, Enum):
OK = "Понятно 😊"
DECLINE = "Отмена 😔"
class YesNo(str, Enum):
YES = "Да 😊"
NO = "Нет 😔"
class Confirmation(str, Enum):
APPROVE = "✅ Подтвердить"
REJECT = "❌ Отклонить"

2
src/core/texts.py Normal file
View File

@ -0,0 +1,2 @@
NO_PERMISSIONS_TO_USE_COMMAND = "У вас нет прав для этой команды."

View File

@ -61,10 +61,9 @@ class Doctors(Base):
back_populates="doctor", cascade="all, delete-orphan")
verification_requests: Mapped[List["VerificationRequests"]] = relationship(
back_populates="doctor", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<Doctors(telegram_id={self.telegram_id!r})>"
return f"<Doctors(telegram_id={self.telegram_id!r}, name={self.name!r}, is_active={self.is_active})>"
class ReferralCode(Base):
@ -93,10 +92,11 @@ class VerificationRequests(Base):
reviewed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
status: Mapped[str] = mapped_column(default=False, nullable=False)
# Связь поправлена — связь с doctor через doctor_id
doctor: Mapped["Doctors"] = relationship(back_populates="verification_requests")
doctor: Mapped["Doctors"] = relationship(
back_populates="verification_requests")
def __repr__(self) -> str:
return f"<VerificationRequests(code={self.code!r})>"
return f"<VerificationRequests(code={self.code!r}, status={self.status!r}, sent_at={self.sent_at!r})>"
class FormLink(Base):

View File

@ -0,0 +1,166 @@
from telegram import Update
from telegram.ext import (
ContextTypes, CommandHandler, ConversationHandler, CallbackQueryHandler,
MessageHandler, filters
)
from core.logging import logger
from core.enums.dialog_helpers import Confirmation
from core.texts import NO_PERMISSIONS_TO_USE_COMMAND
from docbot.services.admins_service import (
get_admin_info, get_doctors_waiting_authorization, approve_doctor,
reject_doctor, get_payment_link
)
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
SELECT_DOCTOR, VERIFY_DOCTOR, SEND_PAYMENT_LINK = range(3)
NO_DOCTORS = "☹️ Нет врачей, ожидающих авторизации."
SELECT_DOCTOR_TO_VERIFY = "👀 Выберите врача для авторизации:"
CHOOSE_DOCTORS_DESTINY = "🙄 Врач с ID {doctor_telegram_id}. Что сделать?"
VERIFIED = "Врач с ID {doctor_telegram_id} верифицирован ✅"
REJECTED = "Верификация врача с ID {doctor_telegram_id} отклонена ❌"
PAYMENT_LINK = "Ссылка на оплату для врача с ID {doctor_telegram_id}: {payment_link}"
async def verify(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
Команда /verify Проверка верификации врача.
Доступна только администратору чата.
"""
user_id = update.effective_user.id
if not await get_admin_info(user_id):
logger.warning(
f"Пользователь {user_id} пытался проверить верификацию без прав.")
await update.message.reply_text(NO_PERMISSIONS_TO_USE_COMMAND)
return
# Здесь должна быть логика проверки верификации врача
# Например, можно запросить информацию о врачах и их статусе верификации
logger.info(f"Admin {user_id} запрашивает список врачей для верификации.")
doctors = await get_doctors_waiting_authorization()
if not doctors:
await update.message.reply_text(NO_DOCTORS)
return
keyboard = [
[
InlineKeyboardButton(
text=doctor.Doctors.name + " " + doctor.VerificationRequests.code,
callback_data=f"verify_{doctor.Doctors.telegram_id}"
)
] for doctor in doctors
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
SELECT_DOCTOR_TO_VERIFY,
reply_markup=reply_markup
)
return SELECT_DOCTOR
async def doctor_selected(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
# Получаем ID врача из callback_data
doctor_telegram_id = int(query.data.split("_")[1])
# (Можно получить объект врача по doctor_id из базы, если надо)
# Кнопки "Верифицировать" и "Отклонить"
keyboard = [
[
InlineKeyboardButton(
Confirmation.APPROVE.value,
callback_data=f"approve_{doctor_telegram_id}"
),
InlineKeyboardButton(
Confirmation.REJECT.value,
callback_data=f"reject_{doctor_telegram_id}"
),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(
CHOOSE_DOCTORS_DESTINY.format(doctor_telegram_id=doctor_telegram_id),
reply_markup=reply_markup,
)
return VERIFY_DOCTOR
async def doctor_action(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
data = query.data.split("_")
action = data[0] # approve или reject
doctor_telegram_id = int(data[1])
context.user_data['doctor_telegram_id'] = doctor_telegram_id
if action == "approve":
# Здесь твоя логика верификации врача (например, запись в базу)
approve_doctor(doctor_telegram_id)
logger.info(f"Врач с ID {doctor_telegram_id} верифицирован.")
# Отправляем сообщение об успешной верификации
await query.edit_message_text(
VERIFIED.format(doctor_telegram_id=doctor_telegram_id)
)
elif action == "reject":
# Логика отклонения
reject_doctor(doctor_telegram_id)
logger.info(f"Врач с ID {doctor_telegram_id} отклонен.")
# Отправляем сообщение об отклонении
await query.edit_message_text(
REJECTED.format(doctor_telegram_id=doctor_telegram_id)
)
return SEND_PAYMENT_LINK
def send_payment_link(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Отправляет ссылку на оплату после верификации врача.
"""
doctor_telegram_id = context.user_data.get('doctor_telegram_id')
if not doctor_telegram_id:
logger.error("Не удалось получить ID врача из user_data.")
return
# Здесь должна быть логика получения ссылки на оплату
payment_link = get_payment_link(doctor_telegram_id)
# Отправляем ссылку на оплату
update.message.reply_text(
PAYMENT_LINK.format(
doctor_telegram_id=doctor_telegram_id,
payment_link=payment_link
)
)
return ConversationHandler.END
def get_verify_handler():
"""
Возвращает список готовых CommandHandler-ов для регистрации в Application.
"""
return ConversationHandler(
entry_points=[CommandHandler("verify", verify)],
states={
SELECT_DOCTOR: [
CallbackQueryHandler(doctor_selected, pattern=r"^verify_\d+$"),
],
VERIFY_DOCTOR: [
CallbackQueryHandler(
doctor_action, pattern=r"^(approve|reject)_\d+$"),
],
SEND_PAYMENT_LINK: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
send_payment_link),
],
},
fallbacks=[],
)

View File

@ -9,6 +9,7 @@ from docbot.handlers.doctors.register_handler import get_register_doctor_first_s
from docbot.handlers.admins.generate_ref import get_referral_handlers
from docbot.handlers.utils.help import get_help_handler
from docbot.handlers.utils.unknown import get_unknown_handler
from docbot.handlers.admins.verify_handler import get_verify_handler
def main():
@ -27,6 +28,7 @@ def main():
app.add_handler(get_doctors_handler())
app.add_handler(get_register_doctor_first_stage_handler())
app.add_handler(get_referral_handlers())
app.add_handler(get_verify_handler())
app.add_handler(get_help_handler())
app.add_handler(get_unknown_handler())
logger.debug("Все хэндлеры зарегистрированы, запускаем polling")

View File

@ -1,8 +1,8 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy import select, Sequence, Row, Tuple
from db.session import AsyncSessionLocal
from db.models import Admins
from db.models import Admins, Doctors, VerificationRequests
async def get_admin_info(telegram_id: int) -> Admins | None:
@ -21,3 +21,59 @@ async def mark_doctor_inactive(telegram_id: int) -> Admins | None:
.where(Admins.telegram_id == telegram_id)
)
return result.scalar_one_or_none()
async def get_doctors_waiting_authorization() -> Sequence[Row[Tuple[VerificationRequests, Doctors]]] | None:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(VerificationRequests, Doctors)
.join(Doctors, VerificationRequests.doctor_id == Doctors.id)
.where(Doctors.is_verified.is_(False), Doctors.is_active.is_(False))
.order_by(VerificationRequests.sent_at.desc())
)
return result.columns(VerificationRequests, Doctors).all()
async def approve_doctor(doctor_id: int) -> bool:
async with AsyncSessionLocal() as session:
async with session.begin():
result = await session.execute(
select(Doctors).where(Doctors.telegram_id == doctor_id)
)
doctor = result.scalar_one_or_none()
if not doctor:
return False
doctor.is_verified = True
doctor.is_active = True
return True
async def reject_doctor(doctor_id: int) -> bool:
async with AsyncSessionLocal() as session:
async with session.begin():
result = await session.execute(
select(Doctors).where(Doctors.telegram_id == doctor_id)
)
doctor = result.scalar_one_or_none()
if not doctor:
return False
doctor.is_verified = False
doctor.is_active = False
return True
async def get_payment_methods(admin_telegram_id: int) -> list[str] | None:
"""
Получает список доступных методов оплаты для врача.
Возвращает список строк или None, если нет методов.
"""
async with AsyncSessionLocal() as session:
result = await session.execute(
select(Admins.available_payment_methods)
.where(Admins.telegram_id == admin_telegram_id)
)
payment_methods = result.scalar_one_or_none()
return payment_methods if payment_methods else []