gendesign/tradein-mvp/.env.example
bot-backend b579fa4ced
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
feat(tradein/tgbot): Telegram support-мост @MERAsupport_bot
Клиент пишет боту в личку → воркер зеркалит сообщение через copyMessage
в топик супергруппы-форума → оператор отвечает реплаем на зеркало → бот
доставляет ответ клиенту. Полный лог переписки в Postgres.

Отдельный контейнер на long-polling, а не webhook в tradein-backend:
не нужно пробивать дырку в auth-middleware (_PUBLIC_PATHS, #2213) и
маршрут в Caddy, нулевая внешняя поверхность, падение бота не задевает API.
Без aiogram — httpx уже в зависимостях, нужны только getUpdates/copyMessage.

Маршрутизация ответа — по topic_message_id: message_id в Telegram уникален
в пределах чата сквозь все топики, а все зеркала лежат в одном support-чате,
поэтому спутать адресата нельзя. Реплай на шапку/на ответ другого оператора
не резолвится (у direction='out' topic_message_id IS NULL) → тихий игнор.

Безопасность (найдено ревью, воспроизведено эмпирически):
- токен Telegram живёт в PATH URL, поэтому sanitize_url его не режет;
  утекал в GlitchTip через locals стек-фреймов (include_local_variables
  по умолчанию True) и через span data HttpxIntegration. Закрыто
  include_local_variables=False + regex-редактор в before_send (обе формы:
  /bot<id>:<secret> и голая <id>:<secret>), поверх существующего PII-scrub.
- httpx-логгер печатает полный URL на INFO → боевой токен уходил бы в
  docker logs каждые 30с. Приглушён до WARNING.

Надёжность:
- kill-switch при пустом токене — idle-блокировка, не exit(0): при
  restart: unless-stopped выход с любым кодом даёт рестарт-луп.
  unless-stopped выбран сознательно — только он гарантирует автозапуск
  после ребута VPS.
- stop_grace_period: 120s — дефолтные 10с убивали бы контейнер раньше,
  чем докрутится long-poll (30с) и отработает drain (100с).
- сбой SQL теперь ловится отдельно и делает rollback перед сдвигом offset:
  иначе сессия в failed-transaction не давала сохранить offset, апдейт
  переигрывался и зеркалился в топик по кругу.

152-ФЗ: переписка — ПДн, ON DELETE CASCADE по chat_id, удаление клиента
одним DELETE. Ретенция — follow-up.

Бот не включается автоматически: TELEGRAM_* задаются в runtime-env на VPS,
без них воркер штатно висит в idle. Порядок — в DEPLOY.md.

Тесты: 51 passed (маршрутизация обоих направлений, дедуп, 403→is_blocked,
throttle-окно шапки, redaction токена во всех формах event).
2026-07-16 16:58:53 +03:00

53 lines
3 KiB
Text
Raw 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.

# Trade-In MVP — environment variables example.
# Скопируй в .env (или экспортируй в shell) для override defaults.
# === Backend ===
DATABASE_URL=postgresql+psycopg://tradein:tradein@postgres:5432/tradein
CORS_ORIGINS=["http://localhost:8080","http://localhost:3000"]
ENVIRONMENT=dev
# Yandex Geocoder API key (25k req/day free tier).
# Required for backfill scripts (scripts/backfill_house_coords.py + audit_address_mismatch.py).
# Empty = Nominatim fallback для backend геокодинга; backfill scripts требуют этот ключ
# и упадут с SystemExit без него.
YANDEX_GEOCODER_API_KEY=
# DaData /clean/address — обогащение target адреса в estimate flow (PR Q1).
# Возвращает canonical-форму, kadastr_num, ФИАС, координаты, ближайшее метро.
# Demo tier: 100 req/день — хватит для тестов и low-traffic prod.
# Если хотя бы один не задан → service возвращает None gracefully, estimator
# продолжает работу без enrichment.
# Получение ключей: https://dadata.ru/api/clean/
DADATA_API_TOKEN=
DADATA_API_SECRET=
# === Scrapers ===
# Транспорт скраперов: "browser" (default — через камуфокс-сайдкар tradein-browser,
# обходит anti-bot/datacenter-IP) или "curl_cffi" (legacy fallback).
# SCRAPER_FETCH_MODE=browser
#
# #915 Stage 3: гонять avito IMV-backfill через /fetch-json сайдкара (in-page fetch
# на avito.ru — same-origin + прокси + warmed-cookies обходят datacenter-403, #562).
# Dormant по умолчанию; включать после прод-валидации IMV-через-браузер.
# AVITO_IMV_USE_BROWSER_FETCHER=false
# === Frontend (build-time) ===
# Backend URL для Next.js rewrites (browser обращается к /api/* — Next.js проксирует на BACKEND_URL/api/*).
# В docker compose это уже выставлено в http://backend:8000 — менять не нужно.
# BACKEND_URL=http://backend:8000
# === Postgres (если меняешь — синхронизируй с DATABASE_URL и docker-compose.yml) ===
POSTGRES_USER=tradein
POSTGRES_PASSWORD=tradein
POSTGRES_DB=tradein
# === Telegram support-bot bridge (tgbot service, docker-compose.prod.yml) ===
# Long-polling worker: пересылает support-обращения в Telegram-топик. Не FastAPI,
# отдельный процесс (app/tgbot_main.py), env читается из backend/.env.runtime на VPS.
#
# BotFather token. Пусто = бот не стартует (выключен).
TELEGRAM_BOT_TOKEN=
# ID супергруппы-форума с включёнными топиками (вида -100XXXXXXXXXX).
TELEGRAM_SUPPORT_CHAT_ID=
# ID топика (thread) внутри супергруппы, куда падают support-сообщения.
TELEGRAM_SUPPORT_TOPIC_ID=