Files
Deal/.superpowers/sdd/channel-discovery/task-7-report.md
T
Rustam Khalimov 27c7831910
ci / build-test (push) Canceled after 0s
Deal — единая кодовая база
SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
2026-09-11 23:56:47 +03:00

7.1 KiB
Raw Blame History

Task 7 — Отчёт: API Discovery

Статус

Выполнено. Роутер /api/discovery реализован, зарегистрирован в main.py, проверен на живом контейнере.

Файлы

  • Создан: backend/app/routers/discovery_routes.py (prefix /api/discovery, tags discovery, auth current_login).
  • Изменён: backend/app/main.py — импорт discovery_routes и добавление в цикл include_router.

Что сделано

Эндпоинты

  • GET /tasks, POST /tasks, PATCH /tasks/{id}, DELETE /tasks/{id};
  • POST /tasks/{id}/start, POST /tasks/{id}/pause;
  • POST /tasks/{id}/generate-keywords — ИИ-генерация ключей по описанию задачи;
  • GET /tasks/{id}/candidates?status=new|review|joined|rejected (необязателен; невалидный статус — 422 через Literal);
  • POST /candidates/{dialog_id}/join — ручное вступление (вне квот/пауз воркера);
  • POST /candidates/{dialog_id}/reject — отклонение с добавлением в чёрный список;
  • GET /blacklist, DELETE /blacklist/{dialog_id};
  • GET /tasks/{id}/log.

Модели и контракт

  • TaskCreate / TaskPatch — camelCase-поля без алиасов (как PreviewBody в tg_routes), необязательные поля None исключаются через model_dump(exclude_none=True), чтобы create_task/patch_task сами подставляли дефолты (в т.ч. threshold/sampleSize из настроек).
  • POST /tasks отдаёт созданную задачу как есть (200); списки — {"items": [...]}; delete — {"ok": true}.
  • Обработка: ValueError → 400, KeyError → 404; для отсутствующих задачи/кандидата — явные 404-хелперы (_task_or_404, _candidate_or_404, чтение кандидата через store: SELECT * FROM disc_candidates WHERE dialog_id=? как в брифе).
  • generate-keywords: если !aiEnabled или у активного провайдера нет ключа (и не local) → {"keywords": [], "error": "..."} с HTTP 200; иначе ai_service.chat_json(промпт RU+EN 1016, user=описание){"keywords": [...]} (чистка: строки, без пустых/длинных/повторов, страховочный лимит 30); ошибка провайдера → {"keywords": [], "error": str}. Пустое описание → error-ветка.
  • join: статус joined → 400; tg.discovery_join(username)tg.add_dialog_monitored(...)discovery.mark_joined(auto=False); ошибка Telegram → 400 с текстом.
  • reject: статус joined → 400 «Уже вступили — удалите источник из каналов»; иначе mark_rejected(reason="отклонено вручную").
  • Роутер чисто проходит ruff check и py_compile.

Вывод проверок

  1. cd /c/telbase && python -m py_compile backend/app/routers/discovery_routes.py backend/app/main.pyPY_COMPILE_OK; ruff check backend/app/routers/discovery_routes.py → clean.
  2. docker compose build appImage telbase-app Built; docker compose up -d app → контейнер пересоздан, /api/health → 200.
  3. Живой API (логин admin/admin, куки):
    • POST /api/discovery/tasks {"name":"","planJoins":1}400 {"detail":"Укажите название задачи"};
    • POST /api/discovery/tasks корректная (plan=1, ключи пустые) → 200, задача status:"draft", дефолты threshold:40/sampleSize:10 подставлены;
    • GET /api/discovery/tasks200 {"items":[задача]};
    • GET /api/discovery/tasks/{id}/candidates?status=review200 {"items":[]}; GET /api/discovery/tasks/{id}/log200 {"items":[]}; GET /api/discovery/blacklist200 {"items":[]};
    • POST /api/discovery/tasks/{id}/generate-keywords200 (в этом окружении ключ ИИ настроен и aiEnabled=true) → реальный вызов провайдера, ответ {"keywords":[14 строк RU+EN]} (happy path);
    • error-ветка: временно PATCH /api/settings {"aiEnabled":false}generate-keywords200 {"keywords":[],"error":"ИИ выключен в настройках (aiEnabled)"}; настройка возвращена в true;
    • POST /api/discovery/tasks/{id}/start при пустых ключах → 400 {"detail":"Нет ключевых слов для поиска — добавьте их в задачу"};
    • PATCH /api/discovery/tasks/{id} (name+keywords) → 200, поля обновлены; POST .../pause200 status:"paused";
    • DELETE /api/discovery/tasks/{id}200 {"ok":true}; повторный GET /tasks200 {"items":[]};
    • start/PATCH/candidates по несуществующей задаче → 404 {"detail":"Задача не найдена"};
    • POST /api/discovery/candidates/{id}/join и /reject по несуществующему кандидату → 404 {"detail":"Кандидат не найден"};
    • GET /api/discovery/tasks/{id}/candidates?status=bogus422 (валидация Literal);
    • DELETE /api/discovery/blacklist/{id} (нет записи) → 200 {"ok":true}.

Concerns

  1. Ветки join/reject с реальным кандидатом и реальным tg.discovery_join (в т.ч. «уже joined» → 400 и ошибка Telegram → 400) живьём не гонялись — нужен подключённый Telegram-аккаунт и настоящий кандидат; это ручная проверка уровня Task 10. Контрактные 404/422 проверены.
  2. generate-keywords в проверке реально дёрнул настроенного провайдера (сетевой вызов). Error-ветка проверена переключением aiEnabled; ветка «ключ не задан» воспроизводится так же, но отдельно не гонялась, чтобы не трогать aiConfigs.
  3. В main.py остались pre-existing предупреждения ruff (не связаны с задачей): неиспользуемый импорт pathlib.Path (F401) и серия # noqa: BLE001 на голых except Exception: без as (RUF100 — ruff не считает BLE001 срабатывающим на таких обработчиках). Не правил: файл вне объёма, py_compile чист.
  4. Под Windows/MSYS кириллица в curl -d '...' ломает тело запроса («There was an error parsing the body») — проверки с кириллицей делались через --data @файл (UTF-8). К продакшену отношения не имеет.