# Design: sources-admin ## Модель данных и источник истины - `sources/sources.yaml` — **источник истины** (в Git). Краулеры при запуске вызывают `sync_sources_to_db()` → таблица `sources`. - Любое веб-изменение: сначала пишем в yaml (`upsert_source_yaml` / `delete_source_yaml` / toggle enabled), затем синкаем yaml → БД (`sync_yaml_to_db_and_back`), чтобы бд-строка не расходилась. ## Изменения в sources/sources.py 1. `delete_source_db(slug)` — удаляет строку из `sources` по slug. Связанные данные: `posts` (FK source_id) — задаём `source_id=NULL` (посты остаются историей, без источника); `runs` — `source_id=NULL`; `rss_state` — DELETE; `classifications` — не трогаем (привязаны к posts). Порядок: `delete_source_yaml` (yaml) → `delete_source_db` (БД). 2. `set_source_enabled(slug, enabled)` — обновляет enabled в yaml (через upsert_source_yaml с ключом enabled) и в БД (`sync_yaml_to_db_and_back`). Пауза: enabled=false; снятие: enabled=true. 3. `add_or_update_source(data)` — валидация (slug обязателен, уникален; crawler ∈ {telegram, rss}; priority ∈ {P0..P3}; для rss — feed_url обязателен) → upsert_source_yaml → sync_yaml_to_db_and_back → возврат slug. ## Веб-слой (web/app.py) - `GET /sources` — страница (за auth): таблица всех источников (slug, name, crawler, direction, lang, priority, enabled, url/feed_url, last_fetch/status), форма добавления, кнопки действий. Шаблон `web/templates/sources.html`. - `POST /sources/add` — добавление (форма). Redirect на /sources. - `POST /sources/update/` — переименование/правка полей. - `POST /sources/{slug}/toggle` — пауза/снятие с паузы. - `POST /sources/{slug}/delete` — удаление (с подтверждением на стороне формы: `onclick="return confirm(...)"`). - Все POST — с `_require_auth`, следуют паттерну /crawlers (async + form). - Валидация ошибок → flash message + редирект (не 500). - Навбар: ссылка «Источники» между «Краулеры» и «Выйти». ## Шаблон sources.html - Таблица + модальный диалог (Bootstrap) для add/edit (одна форма). - Кнопки: Изменить (заполняет модалку), Удалить (confirm), Пауза/Снять (toggle). - Приоритет — селект P0/P1/P2/P3; crawler — селект telegram/rss; direction — селект из DIRECTIONS_CANON (keywords) + «—». - Для rss: поле feed_url; для telegram: channel. ## Безопасность - Пароль ADMIN_PASSWORD, как на /crawlers. - slug — белый список [a-z0-9_-], валидация на добавление (иначе 400). - Удаление — только POST + confirm (никаких GET-удалений).