mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
openspec: архив 14 завершённых change-ов (веб-фиксы, crawler-queue, own-content-hub, publisher-service); спеки влиты в openspec/specs
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-15
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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/<slug>` — переименование/правка полей.
|
||||
- `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-удалений).
|
||||
@@ -0,0 +1,32 @@
|
||||
# Proposal: sources-admin
|
||||
|
||||
## Why
|
||||
|
||||
Сейчас источники редактируются вручную в `sources/sources.yaml` (Git-файл).
|
||||
Веба для управления нет: на /crawlers источники только читаются. Пользователь
|
||||
хочет отдельную страницу управления источниками:
|
||||
|
||||
- добавлять новые источники (telegram/rss), указывая slug, name, url/feed_url, направление, приоритет;
|
||||
- переименовывать (name), менять slug при необходимости;
|
||||
- удалять (с подтверждением);
|
||||
- ставить на паузу (enabled=false) и снимать с паузы;
|
||||
- менять приоритет (priority P0–P3).
|
||||
|
||||
`sources.yaml` остаётся источником истины (краулеры при запуске делают
|
||||
`sync_sources_to_db`). CRUD-функции в sources.py уже есть (upsert_source_yaml,
|
||||
delete_source_yaml, db_source_to_yaml) — не хватает удаления/паузы в БД и
|
||||
веб-слоя. Проблема: удаление источника из yaml оставляет его в БД (сирота с
|
||||
постами/ранами); пауза = enabled=0 в yaml+БД.
|
||||
|
||||
## Success criteria
|
||||
|
||||
- Страница /sources (за auth): таблица всех источников, кнопки Добавить / Изменить / Удалить / Пауза / Снять с паузы.
|
||||
- Добавление/изменение через модальную форму (slug, name, crawler, url/feed_url, direction, lang, priority).
|
||||
- Удаление с подтверждением (confirm) и каскадной чисткой связанных данных.
|
||||
- Каждое изменение пишется в sources.yaml И БД атомарно (yaml — источник истины).
|
||||
- /crawlers продолжает работать (читает ту же таблицу).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Тестирование краулеров из веба (запуск оставлен на /crawlers).
|
||||
- Управление направлениями (направления — код в keywords.py, не CRUD).
|
||||
@@ -0,0 +1,92 @@
|
||||
# Spec: sources-admin
|
||||
|
||||
## Purpose
|
||||
Страница управления источниками в веб-интерфейсе VESTI: добавление, изменение,
|
||||
удаление, пауза и смена приоритета источников без ручной правки YAML.
|
||||
`sources.yaml` остаётся источником истины, веб пишет в него и синкает БД.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Просмотр списка источников
|
||||
Страница /sources показывает таблицу всех источников с их полями и статусом.
|
||||
|
||||
#### Scenario: просмотр списка источников
|
||||
- **Given** веб запущен, пользователь авторизован
|
||||
- **When** он открывает `/sources`
|
||||
- **Then** страница показывает таблицу всех источников: slug, name, crawler,
|
||||
direction, lang, priority, enabled (пауза), url/feed_url, статус (last_fetch/error)
|
||||
|
||||
### Requirement: Добавление нового источника
|
||||
Форма «Добавить» создаёт запись в sources.yaml и БД.
|
||||
|
||||
#### Scenario: добавление нового источника
|
||||
- **Given** страница /sources
|
||||
- **When** форма «Добавить» заполнена (slug, name, crawler=rss, feed_url, direction=tech, priority=P1)
|
||||
- **And** отправлена
|
||||
- **Then** запись появляется в `sources/sources.yaml` и таблице `sources` БД
|
||||
- **And** страница показывает новый источник в таблице
|
||||
|
||||
#### Scenario: валидация добавления
|
||||
- **Given** форма добавления с невалидным slug (`my source!`)
|
||||
- **When** отправлена
|
||||
- **Then** добавление отклонено, показана ошибка (flash), ничего не записано
|
||||
- **And** slug принимается только `[a-z0-9_-]+`
|
||||
|
||||
### Requirement: Изменение полей источника
|
||||
Форма «Изменить» обновляет name/priority/направление и пр. в yaml и БД.
|
||||
|
||||
#### Scenario: переименование / изменение полей
|
||||
- **Given** существующий источник `lwn`
|
||||
- **When** форма «Изменить» меняет `name` на «LWN Tech» и `priority` на P2
|
||||
- **Then** изменения применяются в yaml и БД, таблица обновляется
|
||||
|
||||
### Requirement: Пауза и снятие с паузы
|
||||
Переключатель enabled=false останавливает сбор источника, enabled=true возобновляет.
|
||||
|
||||
#### Scenario: пауза и снятие с паузы
|
||||
- **Given** источник `lwn` (enabled=true)
|
||||
- **When** пользователь нажимает «Пауза»
|
||||
- **Then** `enabled=false` в yaml и БД
|
||||
- **And** краулеры больше не собирают этот источник (`get_enabled_sources` исключает)
|
||||
- **When** пользователь нажимает «Снять с паузы»
|
||||
- **Then** `enabled=true`, сбор возобновляется
|
||||
|
||||
### Requirement: Удаление источника
|
||||
Удаление (с подтверждением) убирает источник из yaml и БД, сохраняя его посты.
|
||||
|
||||
#### Scenario: удаление источника
|
||||
- **Given** источник `opennet` с постами в БД
|
||||
- **When** пользователь подтверждает удаление (confirm)
|
||||
- **Then** запись удалена из yaml и sources БД
|
||||
- **And** его посты НЕ удалены: `source_id=NULL` (история сохраняется)
|
||||
- **And** rss_state для него удалён
|
||||
|
||||
#### Scenario: удаление без подтверждения
|
||||
- **Given** форма удаления
|
||||
- **When** confirm отклонён (Cancel)
|
||||
- **Then** ничего не удалено, данные не меняются
|
||||
|
||||
### Requirement: Доступ без авторизации
|
||||
Страница /sources защищена паролем, как остальные админ-страницы.
|
||||
|
||||
#### Scenario: доступ без авторизации
|
||||
- **Given** пользователь не авторизован
|
||||
- **When** он открывает `/sources`
|
||||
- **Then** происходит редирект на /login (303)
|
||||
|
||||
### Requirement: Приоритет источника
|
||||
Приоритет P0–P3 влияет на порядок обработки источников.
|
||||
|
||||
#### Scenario: приоритет
|
||||
- **Given** источники с priority P0–P3
|
||||
- **When** страница /sources загружена
|
||||
- **Then** приоритеты отображаются и доступны для изменения (P0–P3)
|
||||
- **And** /crawlers сортирует по (enabled, priority, slug) с учётом нового приоритета
|
||||
|
||||
### Requirement: Навигация
|
||||
Ссылка «Источники» присутствует в навбаре.
|
||||
|
||||
#### Scenario: навигация
|
||||
- **Given** любая страница веб (base.html)
|
||||
- **When** открыт навбар
|
||||
- **Then** есть ссылка «Источники» на `/sources`
|
||||
@@ -0,0 +1,35 @@
|
||||
# Tasks: sources-admin
|
||||
|
||||
## 1. sources.py — CRUD для БД + валидация
|
||||
|
||||
- [x] 1.1 `delete_source_db(slug)`: UPDATE posts/runs SET source_id=NULL, DELETE rss_state, DELETE sources.
|
||||
Проверка: юнит-тест — после delete_source_db строки sources нет, rss_state нет, posts.source_id=NULL.
|
||||
- [x] 1.2 `set_source_enabled(slug, enabled)`: обновить yaml (upsert с enabled) + sync_yaml_to_db_and_back.
|
||||
Проверка: юнит — после toggle enabled в БД = 0/1, в yaml = false/true.
|
||||
- [x] 1.3 `add_or_update_source(data)`: валидация (slug regex, crawler ∈ {telegram,rss}, priority ∈ P0..P3, rss→feed_url обязателен) → upsert yaml → sync.
|
||||
Проверка: юнит — add 'opennet' (rss) → есть в yaml и БД; невалидный 400.
|
||||
|
||||
## 2. Веб-слой
|
||||
|
||||
- [x] 2.1 GET /sources — страница (таблица + форма). Шаблон sources.html.
|
||||
Проверка: GET с auth → 200, в таблице все источники (10).
|
||||
- [x] 2.2 POST /sources/add, /sources/update/<slug> — добавление/правка. Валидация, flash, редирект.
|
||||
Проверка: POST add opennet → в БД/yaml появился; POST update — name изменился.
|
||||
- [x] 2.3 POST /sources/{slug}/toggle — пауза (enabled 0) / снятие (enabled 1).
|
||||
Проверка: toggle lwn → enabled=0 в БД и yaml; в /crawlers lwn помечен disabled.
|
||||
- [x] 2.4 POST /sources/{slug}/delete — удаление с confirm. Каскад: posts/runs source_id=NULL, rss_state удалён.
|
||||
Проверка: delete тестового источника → из yaml и БД исчез, посты не потеряны (source_id NULL).
|
||||
- [x] 2.5 Навбар: ссылка «Источники» (была добавлена — проверена в base.html).
|
||||
|
||||
## 3. Интеграция, доки
|
||||
|
||||
- [x] 3.1 AGENT.MD / STATUS.md / TODO.md — /sources (добавлено/закрыто).
|
||||
- [x] 3.2 Ссылка /sources в nav (проверка вёрстки).
|
||||
- [x] 3.3 `openspec validate sources-admin` → valid.
|
||||
- [x] 3.4 Рестарт vesti-web, ручная проверка GET /sources 200.
|
||||
|
||||
## Примечания
|
||||
- sources.yaml — источник истины; каждое изменение пишется в yaml И БД.
|
||||
- Удалённые посты НЕ удаляются (история), только source_id=NULL.
|
||||
- Пауза не трогает уже собранные посты — только прекращает сбор (enabled=0).
|
||||
- HTTP-тест: add→toggle(pause)→toggle(resume)→delete прошёл end-to-end (тест. источник test_tmp_src удалён, чисто).
|
||||
Reference in New Issue
Block a user