Document platform architecture and Telegram proxy local setup.
Add developer docs for CA/CP/PI flows and wire cp-workers to the host SOCKS proxy so local Telegram auth and parsing work reliably. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# Архитектура ЦА (Analytics Center)
|
||||
|
||||
Центр аналитики — ядро MapMil: PostgreSQL, FastAPI, Vue admin/карта, distribution API для ПИ.
|
||||
|
||||
Общий контекст: [docs/architecture-overview.md](../../docs/architecture-overview.md).
|
||||
ПИ отдельно: [DISTRIBUTION.md](DISTRIBUTION.md).
|
||||
|
||||
---
|
||||
|
||||
## Общими чертами
|
||||
|
||||
ЦА:
|
||||
|
||||
- хранит события, jobs, объекты карты, consumers;
|
||||
- отдаёт единственный UI (`ca-frontend`);
|
||||
- ставит задания в Redis для ЦП;
|
||||
- принимает ingest от ЦП;
|
||||
- отдаёт срезы внешним системам через `/api/v1/events`.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[ca-frontend Vue]
|
||||
API[ca-api FastAPI]
|
||||
DB[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
CP[CP workers]
|
||||
|
||||
UI --> API
|
||||
API --> DB
|
||||
API -->|enqueue| Redis
|
||||
Redis --> CP
|
||||
CP -->|/internal/*| API
|
||||
```
|
||||
|
||||
Контейнеры: `ca-db`, `ca-api`, `ca-frontend` (+ общий `redis`).
|
||||
|
||||
---
|
||||
|
||||
## Подробнее
|
||||
|
||||
### Структура
|
||||
|
||||
```text
|
||||
centers/analytics/
|
||||
├── ARCHITECTURE.md
|
||||
├── DISTRIBUTION.md
|
||||
├── api/
|
||||
│ ├── Dockerfile
|
||||
│ ├── requirements.txt
|
||||
│ └── app/
|
||||
│ ├── main.py # lifespan: migrations, scheduler, seed
|
||||
│ ├── database.py
|
||||
│ ├── models.py
|
||||
│ ├── schemas.py
|
||||
│ ├── deps.py # X-Internal-Token
|
||||
│ ├── seed.py
|
||||
│ ├── storage.py # uploads
|
||||
│ ├── routers/
|
||||
│ │ ├── objects.py # /api/health, /api/objects, media
|
||||
│ │ ├── map.py # /api/map/*
|
||||
│ │ ├── admin.py # /admin/*
|
||||
│ │ ├── internal.py # /internal/* (только ЦП)
|
||||
│ │ └── v1.py # /api/v1/* (ПИ)
|
||||
│ └── services/
|
||||
│ ├── jobs.py # Redis RPUSH
|
||||
│ ├── scheduler.py # периодический re-queue
|
||||
│ ├── ingest.py # дедуп + map sync
|
||||
│ ├── filtering.py
|
||||
│ ├── map_query.py
|
||||
│ ├── events_query.py
|
||||
│ ├── analytics.py
|
||||
│ └── migrations.py
|
||||
└── frontend/
|
||||
├── Dockerfile # Vite build + nginx
|
||||
├── nginx.conf # proxy /api /admin /internal → ca-api
|
||||
└── src/
|
||||
├── views/ # Map, Parsers, Events, Analytics, Consumers
|
||||
├── components/ # карта, CRUD объектов
|
||||
├── api/ # HTTP-клиенты
|
||||
└── router/index.ts
|
||||
```
|
||||
|
||||
### Модели данных
|
||||
|
||||
| Модель | Таблица | Назначение |
|
||||
|--------|--------|------------|
|
||||
| `Event` | `events` | Нормализованное событие; UK `source_url` |
|
||||
| `ParseJob` | `parse_jobs` | Конфиг парсера, интервал, статус |
|
||||
| `MapObject` | `map_objects` | Точка на карте (event или ручная) |
|
||||
| `ObjectMedia` | `object_media` | Файлы к объектам |
|
||||
| `Consumer` | `consumers` | Подписчик ПИ (hash ключа) |
|
||||
| `ConsumerFilter` | `consumer_filters` | Фильтры среза ПИ |
|
||||
|
||||
### HTTP-поверхности
|
||||
|
||||
| Prefix | Кто вызывает | Содержание |
|
||||
|--------|--------------|------------|
|
||||
| `/api/*` | UI, публичный health | Карта, объекты, медиа |
|
||||
| `/admin/*` | UI admin | Jobs, events, analytics, consumers |
|
||||
| `/internal/*` | Только ЦП | ingest, job status, listener subscriptions |
|
||||
| `/api/v1/*` | Внешние клиенты | Events с Bearer-ключом |
|
||||
|
||||
Internal защищён заголовком `X-Internal-Token` (`INTERNAL_TOKEN`).
|
||||
|
||||
### Jobs и scheduler
|
||||
|
||||
1. `POST /admin/jobs` / retry → запись `ParseJob` + `enqueue_job` (`services/jobs.py`).
|
||||
2. Очередь: `cp:jobs:{family}` из `contracts/queues.py`.
|
||||
3. `services/scheduler.py` — тик ~30 с, повторная постановка активных jobs по `interval_seconds`.
|
||||
|
||||
### Ingest
|
||||
|
||||
`POST /internal/ingest` → `services/ingest.py`:
|
||||
|
||||
- дедуп по `source_url`;
|
||||
- создание `Event`;
|
||||
- при координатах — sync `MapObject`;
|
||||
- batch обновляет статус job; `listener: true` — нет.
|
||||
|
||||
### Frontend (маршруты)
|
||||
|
||||
| Path | View |
|
||||
|------|------|
|
||||
| `/` | `MapViewPage.vue` |
|
||||
| `/parsers` | `ParsersView.vue` |
|
||||
| `/events` | `EventsView.vue` |
|
||||
| `/analytics` | `AnalyticsView.vue` |
|
||||
| `/consumers` | `ConsumersView.vue` |
|
||||
|
||||
Карта: Leaflet, фильтры дат/региона/темы/источника, CRUD объектов (ПКМ), медиа, таймлайн появления (если включён в UI).
|
||||
|
||||
### Nginx
|
||||
|
||||
`ca-frontend` слушает `:80` (с хоста `:8080`), проксирует backend-пути на `http://ca-api:8000`. Лимит тела для медиа задаётся в `nginx.conf`.
|
||||
|
||||
### Env (ЦА)
|
||||
|
||||
| Переменная | Назначение |
|
||||
|------------|------------|
|
||||
| `DATABASE_URL` | PostgreSQL |
|
||||
| `REDIS_URL` | Очереди |
|
||||
| `INTERNAL_TOKEN` | Auth ЦП ↔ ЦА |
|
||||
| `TEST_PI_API_KEY` | Seed consumer `test-pi` |
|
||||
| `UPLOAD_DIR` | Медиа (по умолчанию `/data/uploads`) |
|
||||
|
||||
### Типовые точки входа в код
|
||||
|
||||
| Задача | Файл |
|
||||
|--------|------|
|
||||
| Новый admin endpoint | `routers/admin.py` |
|
||||
| Логика ingest | `services/ingest.py` |
|
||||
| Фильтры карты | `services/map_query.py` + `routers/map.py` |
|
||||
| Новый экран UI | `frontend/src/views/` + `router/index.ts` |
|
||||
| Поля парсера в форме | `ParsersView.vue` (+ contracts) |
|
||||
@@ -0,0 +1,91 @@
|
||||
# Distribution API (ПИ)
|
||||
|
||||
**ПИ** — не отдельный Docker-сервис, а внешний HTTP-срез поверх данных ЦА.
|
||||
|
||||
Общий контекст: [docs/architecture-overview.md](../../docs/architecture-overview.md).
|
||||
|
||||
---
|
||||
|
||||
## Общими чертами
|
||||
|
||||
1. В UI «ПИ» (`/consumers`) создаётся подписчик.
|
||||
2. Ему выдаётся API-ключ (в БД хранится только SHA-256 hash).
|
||||
3. Клиент читает события:
|
||||
|
||||
```http
|
||||
GET /api/v1/events
|
||||
Authorization: Bearer <api_key>
|
||||
```
|
||||
|
||||
4. ЦА отдаёт события с учётом фильтров consumer (регионы, темы, дата).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client[External system]
|
||||
V1[GET /api/v1/events]
|
||||
Cons[Consumer + Filter]
|
||||
Events[(events)]
|
||||
|
||||
Client -->|Bearer key| V1
|
||||
V1 --> Cons
|
||||
Cons --> Events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Подробнее
|
||||
|
||||
### Код
|
||||
|
||||
| Часть | Путь |
|
||||
|-------|------|
|
||||
| Роут | `centers/analytics/api/app/routers/v1.py` |
|
||||
| Модели | `Consumer`, `ConsumerFilter` в `models.py` |
|
||||
| Фильтрация | `services/filtering.py` |
|
||||
| Admin CRUD | `routers/admin.py` — `/admin/consumers`, `…/rotate-key` |
|
||||
| UI | `frontend/src/views/ConsumersView.vue` |
|
||||
| Seed | `seed.py` — consumer `test-pi` из `TEST_PI_API_KEY` |
|
||||
|
||||
### Аутентификация
|
||||
|
||||
- Заголовок: `Authorization: Bearer <plain_api_key>`.
|
||||
- Сравнение с `consumers.api_key_hash` (SHA-256).
|
||||
- Неактивный consumer → отказ.
|
||||
|
||||
### Фильтры подписчика
|
||||
|
||||
Типичные ограничения (через `ConsumerFilter`):
|
||||
|
||||
- список регионов;
|
||||
- список тем;
|
||||
- `date_from` (нижняя граница даты события).
|
||||
|
||||
Точный набор полей — в модели/схемах admin API; при изменении фильтров обновляйте UI consumers и `filtering.py`.
|
||||
|
||||
### Admin операции
|
||||
|
||||
| Метод | Путь | Действие |
|
||||
|-------|------|----------|
|
||||
| GET | `/admin/consumers` | Список |
|
||||
| POST | `/admin/consumers` | Создать (+ ключ в ответе один раз) |
|
||||
| PATCH | `/admin/consumers/{id}` | Обновить |
|
||||
| POST | `/admin/consumers/{id}/rotate-key` | Новый ключ |
|
||||
|
||||
### Локальный тест
|
||||
|
||||
После `docker compose up` (если не меняли seed):
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: Bearer test-pi-api-key-change-me" \
|
||||
"http://localhost:8080/api/v1/events" | head
|
||||
```
|
||||
|
||||
В проде обязательно смените `TEST_PI_API_KEY` и ротируйте ключи.
|
||||
|
||||
### Что ПИ не делает
|
||||
|
||||
- не пишет в БД;
|
||||
- не ставит parse jobs;
|
||||
- не ходит в Redis / ЦП.
|
||||
|
||||
Только чтение уже ingest'нутых событий через контракт `/api/v1`.
|
||||
Reference in New Issue
Block a user