MVP ready. Fully functional (registration? moderation, profiles vew)
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
This commit is contained in:
141
services/bot/app/handlers/admin.py
Normal file
141
services/bot/app/handlers/admin.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from sqlalchemy import desc
|
||||
|
||||
from app.core.db import db_session
|
||||
from app.repositories.admin_repo import AdminRepository
|
||||
from app.repositories.candidate_repo import CandidateRepository
|
||||
from app.models.candidate import Candidate
|
||||
|
||||
from app.utils.common import csv_to_list
|
||||
|
||||
async def add_admin_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /addadmin <telegram_id> — добавить нового администратора (только для админов)."""
|
||||
with db_session() as s:
|
||||
repo = AdminRepository(s)
|
||||
if not repo.is_admin(update.effective_user.id):
|
||||
await update.message.reply_text("Недостаточно прав.")
|
||||
return
|
||||
|
||||
args = context.args or []
|
||||
if not args:
|
||||
await update.message.reply_text("Формат: /addadmin 123456789")
|
||||
return
|
||||
|
||||
try:
|
||||
tid = int(args[0])
|
||||
except ValueError:
|
||||
await update.message.reply_text("Неверный telegram_id.")
|
||||
return
|
||||
|
||||
from app.models.admin import Admin
|
||||
if s.query(Admin).filter(Admin.telegram_id == tid).first():
|
||||
await update.message.reply_text("Этот администратор уже добавлен.")
|
||||
return
|
||||
|
||||
s.add(Admin(telegram_id=tid))
|
||||
s.commit()
|
||||
await update.message.reply_text(f"Администратор {tid} добавлен.")
|
||||
|
||||
|
||||
async def list_candidates_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /candidates — показать последние анкеты (только для админов)."""
|
||||
with db_session() as s:
|
||||
admin_repo = AdminRepository(s)
|
||||
if not admin_repo.is_admin(update.effective_user.id):
|
||||
await update.message.reply_text("Недостаточно прав.")
|
||||
return
|
||||
|
||||
rows = s.query(Candidate).order_by(desc(Candidate.created_at)).limit(30).all()
|
||||
if not rows:
|
||||
await update.message.reply_text("Кандидатов пока нет.")
|
||||
return
|
||||
|
||||
out = []
|
||||
for c in rows:
|
||||
out.append(
|
||||
f"#{c.id} {c.full_name or '—'} | {c.city or '—'} | цель: {c.goal or '—'} | @{c.username or ''}"
|
||||
)
|
||||
await update.message.reply_text("Последние анкеты:\n" + "\n".join(out))
|
||||
|
||||
|
||||
async def verify_candidate_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /verify <id> — пометить анкету как проверенную (только для админов)."""
|
||||
with db_session() as s:
|
||||
admin_repo = AdminRepository(s)
|
||||
if not admin_repo.is_admin(update.effective_user.id):
|
||||
await update.message.reply_text("Недостаточно прав.")
|
||||
return
|
||||
|
||||
args = context.args or []
|
||||
if not args:
|
||||
await update.message.reply_text("Формат: /verify 12")
|
||||
return
|
||||
|
||||
try:
|
||||
cid = int(args[0])
|
||||
except ValueError:
|
||||
await update.message.reply_text("Укажи числовой id анкеты.")
|
||||
return
|
||||
|
||||
c = s.get(Candidate, cid)
|
||||
if not c:
|
||||
await update.message.reply_text("Анкета не найдена.")
|
||||
return
|
||||
|
||||
c.is_verified = True
|
||||
s.commit()
|
||||
await update.message.reply_text(f"Анкета #{cid} помечена как проверенная.")
|
||||
|
||||
async def view_candidate_handler(update, context):
|
||||
"""Команда /view <id> — показать карточку анкеты с фото (для админов)."""
|
||||
with db_session() as s:
|
||||
admin_repo = AdminRepository(s)
|
||||
if not admin_repo.is_admin(update.effective_user.id):
|
||||
await update.message.reply_text("Недостаточно прав.")
|
||||
return
|
||||
|
||||
args = context.args or []
|
||||
if not args:
|
||||
await update.message.reply_text("Формат: /view 12")
|
||||
return
|
||||
|
||||
try:
|
||||
cid = int(args[0])
|
||||
except ValueError:
|
||||
await update.message.reply_text("Укажи числовой id анкеты.")
|
||||
return
|
||||
|
||||
c = s.get(Candidate, cid)
|
||||
if not c:
|
||||
await update.message.reply_text("Анкета не найдена.")
|
||||
return
|
||||
|
||||
# Текстовая карточка
|
||||
caption = (
|
||||
f"#{c.id} {c.full_name or '—'} @{c.username or ''}\n"
|
||||
f"Город: {c.city or '—'} | Цель: {c.goal or '—'}\n"
|
||||
f"Статус: {'✅ проверена' if c.is_verified else '⏳ на проверке'} | Активна: {'да' if c.is_active else 'нет'}"
|
||||
)
|
||||
|
||||
chat_id = update.effective_chat.id
|
||||
|
||||
# Аватар (если есть)
|
||||
if c.avatar_file_id:
|
||||
await context.bot.send_photo(chat_id=chat_id, photo=c.avatar_file_id, caption=caption)
|
||||
else:
|
||||
await context.bot.send_message(chat_id=chat_id, text=caption + "\n(аватар отсутствует)")
|
||||
|
||||
# Галерея (если есть)
|
||||
gallery_ids = csv_to_list(c.gallery_file_ids)
|
||||
if gallery_ids:
|
||||
# Telegram принимает до 10 media за раз
|
||||
chunk = []
|
||||
for fid in gallery_ids:
|
||||
chunk.append(InputMediaPhoto(media=fid))
|
||||
if len(chunk) == 10:
|
||||
await context.bot.send_media_group(chat_id=chat_id, media=chunk)
|
||||
chunk = []
|
||||
if chunk:
|
||||
await context.bot.send_media_group(chat_id=chat_id, media=chunk)
|
||||
348
services/bot/app/handlers/conversation.py
Normal file
348
services/bot/app/handlers/conversation.py
Normal file
@@ -0,0 +1,348 @@
|
||||
from __future__ import annotations
|
||||
from datetime import datetime, date
|
||||
from typing import List
|
||||
|
||||
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, InputMediaPhoto
|
||||
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters
|
||||
|
||||
from app.core.db import db_session
|
||||
from app.models.candidate import Candidate
|
||||
from app.repositories.admin_repo import AdminRepository
|
||||
from app.utils.common import csv_to_list, list_to_csv, calc_age
|
||||
from app.services.admin_bootstrap import ensure_primary_admin
|
||||
|
||||
(
|
||||
S_NAME, S_GENDER, S_BIRTH, S_HEIGHT, S_WEIGHT,
|
||||
S_COUNTRY, S_CITY, S_CITIZEN, S_VISA, S_LANGS,
|
||||
S_EDU, S_OCCUP, S_INCOME, S_RELIGION, S_MARITAL, S_CHILDREN, S_CHILDREN_NOTES,
|
||||
S_SMOKING, S_ALCOHOL, S_HEALTH,
|
||||
S_HOBBIES_TAGS, S_HOBBIES_FREE,
|
||||
S_GOAL, S_PARTNER,
|
||||
S_AVATAR, S_GALLERY, S_CONSENTS, S_CONFIRM
|
||||
) = range(28)
|
||||
|
||||
GENDER_KB = [["жен", "муж"], ["другое"]]
|
||||
YESNO_KB = [["да", "нет"]]
|
||||
SMOKE_ALC_KB = [["нет", "иногда", "да"]]
|
||||
MARITAL_KB = [["никогда не был(а) в браке"], ["в разводе"], ["вдова/вдовец"]]
|
||||
INCOME_KB = [["до 2 млн KRW", "2–4 млн"], ["4–6 млн", "6+ млн"], ["не указывать"]]
|
||||
|
||||
def build_conversation() -> ConversationHandler:
|
||||
return ConversationHandler(
|
||||
entry_points=[CommandHandler("start", start), CommandHandler("new", start)],
|
||||
states={
|
||||
S_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_name)],
|
||||
S_GENDER: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_gender)],
|
||||
S_BIRTH: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_birth)],
|
||||
S_HEIGHT: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_height)],
|
||||
S_WEIGHT: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_weight)],
|
||||
S_COUNTRY: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_country)],
|
||||
S_CITY: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_city)],
|
||||
S_CITIZEN: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_citizen)],
|
||||
S_VISA: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_visa)],
|
||||
S_LANGS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_langs)],
|
||||
S_EDU: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_edu)],
|
||||
S_OCCUP: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_occup)],
|
||||
S_INCOME: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_income)],
|
||||
S_RELIGION: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_religion)],
|
||||
S_MARITAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_marital)],
|
||||
S_CHILDREN: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_children)],
|
||||
S_CHILDREN_NOTES: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_children_notes)],
|
||||
S_SMOKING: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_smoking)],
|
||||
S_ALCOHOL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_alcohol)],
|
||||
S_HEALTH: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_health)],
|
||||
S_HOBBIES_TAGS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_hobbies_tags)],
|
||||
S_HOBBIES_FREE: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_hobbies_free)],
|
||||
S_GOAL: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_goal)],
|
||||
S_PARTNER: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_partner)],
|
||||
S_AVATAR: [
|
||||
MessageHandler(filters.PHOTO, s_avatar),
|
||||
MessageHandler(filters.TEXT & ~filters.COMMAND, s_avatar),
|
||||
],
|
||||
S_GALLERY: [
|
||||
MessageHandler(filters.PHOTO, s_gallery),
|
||||
MessageHandler(filters.TEXT & ~filters.COMMAND, s_gallery),
|
||||
],
|
||||
S_CONSENTS: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_consents)],
|
||||
S_CONFIRM: [MessageHandler(filters.TEXT & ~filters.COMMAND, s_confirm)],
|
||||
},
|
||||
fallbacks=[CommandHandler("cancel", cancel)],
|
||||
allow_reentry=True,
|
||||
)
|
||||
|
||||
async def start(update, context):
|
||||
with db_session() as s:
|
||||
ensure_primary_admin(s)
|
||||
exists = s.query(Candidate).filter(Candidate.telegram_id == update.effective_user.id).first()
|
||||
if exists:
|
||||
await update.message.reply_text(
|
||||
"Анкета уже существует. Посмотреть — /my, редактирование — /edit (скоро), начать заново — /new",
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
await update.message.reply_text(
|
||||
"Привет! Это анкета для брачного сервиса с сопровождением до визы F‑6 🇰🇷\nКак тебя зовут?",
|
||||
reply_markup=ReplyKeyboardRemove(),
|
||||
)
|
||||
return S_NAME
|
||||
|
||||
async def s_name(update, context):
|
||||
context.user_data["full_name"] = update.message.text.strip()
|
||||
await update.message.reply_text("Твой пол?", reply_markup=ReplyKeyboardMarkup(GENDER_KB, one_time_keyboard=True, resize_keyboard=True))
|
||||
return S_GENDER
|
||||
|
||||
async def s_gender(update, context):
|
||||
context.user_data["gender"] = update.message.text.strip().lower()
|
||||
await update.message.reply_text("Дата рождения (ДД.ММ.ГГГГ):", reply_markup=ReplyKeyboardRemove())
|
||||
return S_BIRTH
|
||||
|
||||
async def s_birth(update, context):
|
||||
try:
|
||||
d = datetime.strptime(update.message.text.strip(), "%d.%m.%Y").date()
|
||||
except Exception:
|
||||
await update.message.reply_text("Неверный формат. Пример: 31.12.1995")
|
||||
return S_BIRTH
|
||||
context.user_data["birth_date"] = d
|
||||
await update.message.reply_text("Рост (см):")
|
||||
return S_HEIGHT
|
||||
|
||||
async def s_height(update, context):
|
||||
try:
|
||||
context.user_data["height_cm"] = float(update.message.text.replace(",", "."))
|
||||
except Exception:
|
||||
await update.message.reply_text("Введи число, например 168")
|
||||
return S_HEIGHT
|
||||
await update.message.reply_text("Вес (кг):")
|
||||
return S_WEIGHT
|
||||
|
||||
async def s_weight(update, context):
|
||||
try:
|
||||
context.user_data["weight_kg"] = float(update.message.text.replace(",", "."))
|
||||
except Exception:
|
||||
await update.message.reply_text("Введи число, например 55")
|
||||
return S_WEIGHT
|
||||
await update.message.reply_text("Страна проживания:")
|
||||
return S_COUNTRY
|
||||
|
||||
async def s_country(update, context):
|
||||
context.user_data["country"] = update.message.text.strip()
|
||||
await update.message.reply_text("Город:")
|
||||
return S_CITY
|
||||
|
||||
async def s_city(update, context):
|
||||
context.user_data["city"] = update.message.text.strip()
|
||||
await update.message.reply_text("Гражданство (РФ/Казахстан/Узбекистан и т.п.):")
|
||||
return S_CITIZEN
|
||||
|
||||
async def s_citizen(update, context):
|
||||
context.user_data["citizenship"] = update.message.text.strip()
|
||||
await update.message.reply_text("Текущий визовый статус в Корее (нет/F‑4/H‑1 и т.п.):")
|
||||
return S_VISA
|
||||
|
||||
async def s_visa(update, context):
|
||||
context.user_data["visa_status"] = update.message.text.strip()
|
||||
await update.message.reply_text("Языки (через запятую, напр.: ru, ko, en):")
|
||||
return S_LANGS
|
||||
|
||||
async def s_langs(update, context):
|
||||
context.user_data["languages"] = list_to_csv(csv_to_list(update.message.text))
|
||||
await update.message.reply_text("Образование (высшее/колледж/иное):")
|
||||
return S_EDU
|
||||
|
||||
async def s_edu(update, context):
|
||||
context.user_data["education"] = update.message.text.strip()
|
||||
await update.message.reply_text("Профессия:")
|
||||
return S_OCCUP
|
||||
|
||||
async def s_occup(update, context):
|
||||
context.user_data["occupation"] = update.message.text.strip()
|
||||
await update.message.reply_text(
|
||||
"Доход (диапазон):",
|
||||
reply_markup=ReplyKeyboardMarkup(INCOME_KB, one_time_keyboard=True, resize_keyboard=True),
|
||||
)
|
||||
return S_INCOME
|
||||
|
||||
async def s_income(update, context):
|
||||
context.user_data["income_range"] = update.message.text.strip()
|
||||
await update.message.reply_text("Религия (если нет, напиши «нет»):", reply_markup=ReplyKeyboardRemove())
|
||||
return S_RELIGION
|
||||
|
||||
async def s_religion(update, context):
|
||||
context.user_data["religion"] = update.message.text.strip()
|
||||
await update.message.reply_text(
|
||||
"Семейное положение:",
|
||||
reply_markup=ReplyKeyboardMarkup(MARITAL_KB, one_time_keyboard=True, resize_keyboard=True),
|
||||
)
|
||||
return S_MARITAL
|
||||
|
||||
async def s_marital(update, context):
|
||||
context.user_data["marital_status"] = update.message.text.strip()
|
||||
await update.message.reply_text("Есть дети?", reply_markup=ReplyKeyboardMarkup(YESNO_KB, one_time_keyboard=True, resize_keyboard=True))
|
||||
return S_CHILDREN
|
||||
|
||||
async def s_children(update, context):
|
||||
ans = update.message.text.strip().lower()
|
||||
context.user_data["has_children"] = (ans == "да")
|
||||
if ans == "да":
|
||||
await update.message.reply_text("Коротко про детей (кол-во/возраст/с кем проживают):", reply_markup=ReplyKeyboardRemove())
|
||||
return S_CHILDREN_NOTES
|
||||
context.user_data["children_notes"] = None
|
||||
await update.message.reply_text("Курение:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True))
|
||||
return S_SMOKING
|
||||
|
||||
async def s_children_notes(update, context):
|
||||
context.user_data["children_notes"] = update.message.text.strip()
|
||||
await update.message.reply_text("Курение:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True))
|
||||
return S_SMOKING
|
||||
|
||||
async def s_smoking(update, context):
|
||||
context.user_data["smoking"] = update.message.text.strip().lower()
|
||||
await update.message.reply_text("Алкоголь:", reply_markup=ReplyKeyboardMarkup(SMOKE_ALC_KB, one_time_keyboard=True, resize_keyboard=True))
|
||||
return S_ALCOHOL
|
||||
|
||||
async def s_alcohol(update, context):
|
||||
context.user_data["alcohol"] = update.message.text.strip().lower()
|
||||
await update.message.reply_text("Здоровье/особенности (если есть; иначе «нет»):", reply_markup=ReplyKeyboardRemove())
|
||||
return S_HEALTH
|
||||
|
||||
async def s_health(update, context):
|
||||
context.user_data["health_notes"] = update.message.text.strip()
|
||||
await update.message.reply_text("Хобби/интересы (теги через запятую, напр.: пение, путешествия, k‑drama):")
|
||||
return S_HOBBIES_TAGS
|
||||
|
||||
async def s_hobbies_tags(update, context):
|
||||
context.user_data["hobbies_tags"] = list_to_csv(csv_to_list(update.message.text))
|
||||
await update.message.reply_text("Расскажи подробнее про интересы (свободный текст):")
|
||||
return S_HOBBIES_FREE
|
||||
|
||||
async def s_hobbies_free(update, context):
|
||||
context.user_data["hobbies_free"] = update.message.text.strip()
|
||||
await update.message.reply_text("Твоя основная цель (например: «брак с гражданином Кореи (F‑6)»):")
|
||||
return S_GOAL
|
||||
|
||||
async def s_goal(update, context):
|
||||
context.user_data["goal"] = update.message.text.strip()
|
||||
await update.message.reply_text("Пожелания к партнёру (возраст, ценности, город, религия и т.п.):")
|
||||
return S_PARTNER
|
||||
|
||||
async def s_partner(update, context):
|
||||
context.user_data["partner_prefs"] = update.message.text.strip()
|
||||
await update.message.reply_text("Загрузи своё фото (аватар):")
|
||||
return S_AVATAR
|
||||
|
||||
async def s_avatar(update, context):
|
||||
if not update.message.photo:
|
||||
await update.message.reply_text("Пожалуйста, пришли фотографию.")
|
||||
return S_AVATAR
|
||||
context.user_data["avatar_file_id"] = update.message.photo[-1].file_id
|
||||
context.user_data["gallery_file_ids"] = []
|
||||
await update.message.reply_text("Можно добавить до 9 дополнительных фото. Пришли их. Когда закончишь — напиши «готово».")
|
||||
return S_GALLERY
|
||||
|
||||
async def s_gallery(update, context):
|
||||
if update.message.photo:
|
||||
fid = update.message.photo[-1].file_id
|
||||
gallery: List[str] = context.user_data.get("gallery_file_ids", [])
|
||||
if len(gallery) < 9:
|
||||
gallery.append(fid)
|
||||
context.user_data["gallery_file_ids"] = gallery
|
||||
await update.message.reply_text(f"Добавлено фото ({len(gallery)}/9). Ещё? Или напиши «готово».")
|
||||
else:
|
||||
await update.message.reply_text("Лимит 9 фото достигнут. Напиши «готово».")
|
||||
return S_GALLERY
|
||||
|
||||
if (update.message.text or "").strip().lower() != "готово":
|
||||
await update.message.reply_text("Если с фото закончили — напиши «готово».")
|
||||
return S_GALLERY
|
||||
|
||||
await update.message.reply_text("Подтверди согласие на обработку персональных данных. Напиши: «согласен» или «согласна».")
|
||||
return S_CONSENTS
|
||||
|
||||
async def s_consents(update, context):
|
||||
ans = (update.message.text or "").strip().lower()
|
||||
if ans not in ("согласен", "согласна"):
|
||||
await update.message.reply_text("Нужно написать «согласен» или «согласна».")
|
||||
return S_CONSENTS
|
||||
context.user_data["consent_personal"] = True
|
||||
context.user_data["consent_policy"] = True
|
||||
|
||||
d = context.user_data
|
||||
age = calc_age(d.get("birth_date"))
|
||||
summary = (
|
||||
f"Проверь данные:\n\n"
|
||||
f"Имя: {d.get('full_name')}\n"
|
||||
f"Пол: {d.get('gender')}\n"
|
||||
f"Дата рождения: {d.get('birth_date')} (возраст: {age})\n"
|
||||
f"Рост/вес: {d.get('height_cm')} см / {d.get('weight_kg')} кг\n"
|
||||
f"Страна/город: {d.get('country')}, {d.get('city')}\n"
|
||||
f"Гражданство: {d.get('citizenship')}\n"
|
||||
f"Виза/статус: {d.get('visa_status')}\n"
|
||||
f"Языки: {d.get('languages')}\n"
|
||||
f"Образование/Профессия: {d.get('education')} / {d.get('occupation')}\n"
|
||||
f"Доход: {d.get('income_range')}\n"
|
||||
f"Религия: {d.get('religion')}\n"
|
||||
f"Семейное положение: {d.get('marital_status')}\n"
|
||||
f"Дети: {'да' if d.get('has_children') else 'нет'} {'(' + d.get('children_notes') + ')' if d.get('children_notes') else ''}\n"
|
||||
f"Курение/Алкоголь: {d.get('smoking')} / {d.get('alcohol')}\n"
|
||||
f"Здоровье: {d.get('health_notes')}\n"
|
||||
f"Хобби (теги): {d.get('hobbies_tags')}\n"
|
||||
f"Интересы (текст): {d.get('hobbies_free')}\n"
|
||||
f"Цель: {d.get('goal')}\n"
|
||||
f"Пожелания к партнёру: {d.get('partner_prefs')}\n"
|
||||
f"\nЕсли всё ок — напиши «подтверждаю». Иначе /cancel и /new."
|
||||
)
|
||||
await update.message.reply_text(summary)
|
||||
return S_CONFIRM
|
||||
|
||||
async def s_confirm(update, context):
|
||||
if (update.message.text or "").strip().lower() != "подтверждаю":
|
||||
await update.message.reply_text('Напиши «подтверждаю» для сохранения, либо /cancel.')
|
||||
return S_CONFIRM
|
||||
|
||||
u = update.effective_user
|
||||
d = context.user_data
|
||||
with db_session() as s:
|
||||
cand = s.query(Candidate).filter(Candidate.telegram_id == u.id).first()
|
||||
if not cand:
|
||||
cand = Candidate(telegram_id=u.id, username=u.username or None)
|
||||
s.add(cand)
|
||||
|
||||
cand.full_name = d.get("full_name")
|
||||
cand.gender = d.get("gender")
|
||||
cand.birth_date = d.get("birth_date")
|
||||
cand.height_cm = d.get("height_cm")
|
||||
cand.weight_kg = d.get("weight_kg")
|
||||
cand.country = d.get("country")
|
||||
cand.city = d.get("city")
|
||||
cand.citizenship = d.get("citizenship")
|
||||
cand.visa_status = d.get("visa_status")
|
||||
cand.languages = d.get("languages")
|
||||
cand.education = d.get("education")
|
||||
cand.occupation = d.get("occupation")
|
||||
cand.income_range = d.get("income_range")
|
||||
cand.religion = d.get("religion")
|
||||
cand.marital_status = d.get("marital_status")
|
||||
cand.has_children = d.get("has_children")
|
||||
cand.children_notes = d.get("children_notes")
|
||||
cand.smoking = d.get("smoking")
|
||||
cand.alcohol = d.get("alcohol")
|
||||
cand.health_notes = d.get("health_notes")
|
||||
cand.hobbies_tags = d.get("hobbies_tags")
|
||||
cand.hobbies_free = d.get("hobbies_free")
|
||||
cand.goal = d.get("goal")
|
||||
cand.partner_prefs = d.get("partner_prefs")
|
||||
cand.avatar_file_id = d.get("avatar_file_id")
|
||||
cand.gallery_file_ids = list_to_csv(d.get("gallery_file_ids", []))
|
||||
cand.consent_personal = d.get("consent_personal", False)
|
||||
cand.consent_policy = d.get("consent_policy", False)
|
||||
cand.is_verified = False
|
||||
cand.is_active = True
|
||||
s.commit()
|
||||
|
||||
await update.message.reply_text("Готово! Анкета сохранена. Свяжемся после модерации. Спасибо!")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def cancel(update, context):
|
||||
await update.message.reply_text("Ок, отменил. Для нового старта — /new")
|
||||
return ConversationHandler.END
|
||||
41
services/bot/app/handlers/profile.py
Normal file
41
services/bot/app/handlers/profile.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
from telegram import Update, InputMediaPhoto
|
||||
from telegram.ext import ContextTypes
|
||||
|
||||
from app.core.db import db_session
|
||||
from app.repositories.candidate_repo import CandidateRepository
|
||||
from app.utils.common import csv_to_list
|
||||
|
||||
|
||||
async def my_profile_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /my — показать свою анкету."""
|
||||
with db_session() as s:
|
||||
repo = CandidateRepository(s)
|
||||
c = repo.by_tg(update.effective_user.id)
|
||||
if not c:
|
||||
await update.message.reply_text("Анкета не найдена. Начни с /new")
|
||||
return
|
||||
|
||||
text = (
|
||||
f"Твоя анкета:\n"
|
||||
f"{c.full_name or '—'} (@{update.effective_user.username or ''})\n"
|
||||
f"Город: {c.city or '—'}\n"
|
||||
f"Цель: {c.goal or '—'}"
|
||||
)
|
||||
|
||||
if c.avatar_file_id:
|
||||
await update.message.reply_photo(photo=c.avatar_file_id, caption=text)
|
||||
else:
|
||||
await update.message.reply_text(text)
|
||||
|
||||
gallery = csv_to_list(c.gallery_file_ids)
|
||||
if gallery:
|
||||
media = [InputMediaPhoto(g) for g in gallery[:10]]
|
||||
await update.message.reply_media_group(media)
|
||||
|
||||
|
||||
async def edit_hint_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /edit — пока только подсказка о редактировании."""
|
||||
await update.message.reply_text(
|
||||
"Точечное редактирование будет добавлено командами /edit_*. Пока что для правок — /new (перезаполнить анкету)."
|
||||
)
|
||||
Reference in New Issue
Block a user