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

53 lines
7.1 KiB
Markdown
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.
# 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.py``PY_COMPILE_OK`; `ruff check backend/app/routers/discovery_routes.py` → clean.
2. `docker compose build app``Image 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/tasks`**200** `{"items":[задача]}`;
- `GET /api/discovery/tasks/{id}/candidates?status=review`**200** `{"items":[]}`; `GET /api/discovery/tasks/{id}/log`**200** `{"items":[]}`; `GET /api/discovery/blacklist`**200** `{"items":[]}`;
- `POST /api/discovery/tasks/{id}/generate-keywords`**200** (в этом окружении ключ ИИ настроен и `aiEnabled=true`) → реальный вызов провайдера, ответ `{"keywords":[14 строк RU+EN]}` (happy path);
- error-ветка: временно `PATCH /api/settings {"aiEnabled":false}``generate-keywords`**200** `{"keywords":[],"error":"ИИ выключен в настройках (aiEnabled)"}`; настройка возвращена в `true`;
- `POST /api/discovery/tasks/{id}/start` при пустых ключах → **400** `{"detail":"Нет ключевых слов для поиска — добавьте их в задачу"}`;
- `PATCH /api/discovery/tasks/{id}` (name+keywords) → **200**, поля обновлены; `POST .../pause`**200** `status:"paused"`;
- `DELETE /api/discovery/tasks/{id}`**200** `{"ok":true}`; повторный `GET /tasks`**200** `{"items":[]}`;
- `start`/`PATCH`/`candidates` по несуществующей задаче → **404** `{"detail":"Задача не найдена"}`;
- `POST /api/discovery/candidates/{id}/join` и `/reject` по несуществующему кандидату → **404** `{"detail":"Кандидат не найден"}`;
- `GET /api/discovery/tasks/{id}/candidates?status=bogus`**422** (валидация `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). К продакшену отношения не имеет.