Add Telegram listener, scheduler, and extended parsers admin UI.

Introduce background scheduling and migrations for analytics ingest, expand parser management and map toolbar UX, and ignore local data/session files from version control.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-02 20:47:56 +03:00
co-authored by Cursor
parent 1d6d54ab9f
commit 1492576fd9
23 changed files with 1124 additions and 267 deletions
+33 -10
View File
@@ -18,7 +18,7 @@ flowchart LR
| Центр | Контейнеры | Назначение |
|-------|------------|------------|
| **ЦА** | `ca-db`, `ca-api`, `ca-frontend` | PostgreSQL, ingest API, карта, distribution API |
| **ЦП** | `cp-workers` | Парсинг Telegram (`centers/parsing/workers`) |
| **ЦП** | `cp-workers` | Парсинг Telegram: real-time listener (Telethon) + batch-задания из Redis |
| **Общее** | `redis` | Очередь заданий ЦА → ЦП |
## Структура monorepo
@@ -67,7 +67,7 @@ docker compose up --build
| Раздел | Путь | Описание |
|--------|------|----------|
| **Карта** | `/` | Интерактивная карта событий: навигация по датам (flatpickr), пресеты периода, фильтры региона/темы/источника, подложки Яндекс/OSM/Topo/ESRI, линейка, полноэкранный режим, центрирование по координатам и городам, поиск населённых пунктов (Nominatim). CRUD для ручных объектов (ПКМ). Поддерживает `?eventId=` |
| **Парсеры** | `/parsers` | Создание заданий Telegram-парсинга, таблица статусов с автообновлением (5 с), повтор failed-заданий |
| **Парсеры** | `/parsers` | Telegram-парсеры с периодическим запуском, настройка интервала, редактирование и удаление; дубликаты по `source_url` не записываются |
| **События** | `/events` | Фильтрация, пагинация, просмотр деталей, ссылка «На карте» для событий с координатами |
| **Аналитика** | `/analytics` | KPI-карточки, график динамики ingest за 30 дней, топ населённых пунктов и регионов |
| **ПИ** | `/consumers` | CRUD подписчиков distribution API, ротация ключей, тест среза через `/api/v1/events` |
@@ -108,8 +108,10 @@ docker compose up --build
| Метод | Путь | Описание |
|-------|------|----------|
| POST | `/admin/jobs` | Создать задание парсинга (ставится в Redis) |
| GET | `/admin/jobs` | Список заданий |
| POST | `/admin/jobs` | Создать парсер (сразу в очередь + периодический запуск) |
| GET | `/admin/jobs` | Список парсеров |
| PATCH | `/admin/jobs/{id}` | Изменить канал/лимит, `interval_seconds`, `is_active` |
| DELETE | `/admin/jobs/{id}` | Удалить парсер |
| POST | `/admin/jobs/{id}/retry` | Повторить failed-задание |
| GET | `/admin/events` | События с фильтрами (`{ items, total }`) |
| GET | `/admin/analytics/summary` | KPI-сводка |
@@ -148,14 +150,35 @@ curl http://localhost:8080/api/v1/events \
-H 'Authorization: Bearer test-pi-api-key-change-me'
```
## Парсинг Telegram
`cp-workers` объединяет два режима в **одном процессе** (общая сессия `telegram.session`):
| Режим | Как работает |
|-------|----------------|
| **Listener (real-time)** | Telethon `NewMessage` / `Album` на активных парсерах (`is_active=true`); новый пост сразу уходит в ingest |
| **Batch (по расписанию)** | Планировщик в `ca-api` ставит задание в Redis; воркер забирает последние N постов (`iter_messages`) |
Дубликаты по `source_url` при ingest пропускаются. Listener не меняет `status` парсера (флаг `listener: true` в ingest).
Переменные `cp-workers`:
| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `TELEGRAM_LISTENER_ENABLED` | `true` | Включить real-time listener |
| `TELEGRAM_LISTENER_REFRESH_SECONDS` | `60` | Как часто обновлять список каналов из БД |
Internal API: `GET /internal/listener/subscriptions` — список активных каналов для listener.
## Поток данных
1. Аналитик создаёт задание: `POST /admin/jobs`
2. `ca-api` ставит задание в Redis (`cp:jobs`)
3. `cp-workers` забирает задание, парсит Telegram через существующую сессию
4. Результаты отправляются в `POST /internal/ingest`
5. События с координатами автоматически появляются на карте как `MapObject`
6. Внешние ПИ получают отфильтрованный срез через `/api/v1/events`
1. Аналитик создаёт парсер: `POST /admin/jobs` (канал, лимит, интервал в секундах)
2. **Listener** сразу подписывается на канал и ingest-ит новые посты
3. **Планировщик** `ca-api` периодически ставит batch-задание в Redis (`cp:jobs`)
4. `cp-workers` забирает batch-задание, парсит последние N постов
5. Результаты → `POST /internal/ingest` (дубликаты по `source_url` пропускаются)
6. Новые события с координатами появляются на карте как `MapObject`
7. Внешние ПИ получают срез через `/api/v1/events`
## Миграция EventRecord → Event