ci / build-test (push) Canceled after 0s
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 зелёные.
7.1 KiB
7.1 KiB
Task 7 — Отчёт: API Discovery
Статус
Выполнено. Роутер /api/discovery реализован, зарегистрирован в main.py, проверен на живом контейнере.
Файлы
- Создан:
backend/app/routers/discovery_routes.py(prefix/api/discovery, tagsdiscovery, authcurrent_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 10–16, 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.
Вывод проверок
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.docker compose build app→Image telbase-app Built;docker compose up -d app→ контейнер пересоздан,/api/health→ 200.- Живой 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→ 200status:"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
- Ветки
join/rejectс реальным кандидатом и реальнымtg.discovery_join(в т.ч. «уже joined» → 400 и ошибка Telegram → 400) живьём не гонялись — нужен подключённый Telegram-аккаунт и настоящий кандидат; это ручная проверка уровня Task 10. Контрактные 404/422 проверены. generate-keywordsв проверке реально дёрнул настроенного провайдера (сетевой вызов). Error-ветка проверена переключениемaiEnabled; ветка «ключ не задан» воспроизводится так же, но отдельно не гонялась, чтобы не трогатьaiConfigs.- В
main.pyостались pre-existing предупреждения ruff (не связаны с задачей): неиспользуемый импортpathlib.Path(F401) и серия# noqa: BLE001на голыхexcept Exception:безas(RUF100 — ruff не считает BLE001 срабатывающим на таких обработчиках). Не правил: файл вне объёма,py_compileчист. - Под Windows/MSYS кириллица в
curl -d '...'ломает тело запроса («There was an error parsing the body») — проверки с кириллицей делались через--data @файл(UTF-8). К продакшену отношения не имеет.