Files
opora/docs/tech-design/02-api.md
T

371 lines
14 KiB
Markdown

# Технический проект. Документ 2: Контракты API (REST)
**Проект:** АС «Платформа ОПОРА РОССИИ»
**Стек:** FastAPI, единое API для бота и сайта
**Дата:** 26.09.2026
**Статус:** черновик к согласованию
---
## 1. Общие принципы
- **Base URL:** `https://opora.my-dpr.ru/api/v1`
- **Формат:** JSON (`application/json`), файлы — `multipart/form-data`.
- **Аутентификация:** `Authorization: Bearer <JWT>` (сессия после входа через бота).
- **Версионирование:** префикс `/v1`.
- **Пагинация:** `?page=1&per_page=50` → ответ `{ items, total, page, per_page }`.
- **Сортировка/фильтры:** `?sort=-created_at&region_id=12&status=new`.
- **Идемпотентность:** заголовок `Idempotency-Key` для POST, создающих сущности.
- **Время:** ISO 8601 UTC (`2026-09-26T12:00:00Z`).
### Формат ошибки
```json
{
"error": {
"code": "validation_error",
"message": "Поле due_date обязательно",
"details": { "field": "due_date" }
}
}
```
### Коды ответов
| Код | Значение |
|---|---|
| 200 | OK |
| 201 | Создано |
| 204 | Удалено (без тела) |
| 400 | Некорректный запрос |
| 401 | Не авторизован |
| 403 | Нет прав |
| 404 | Не найдено |
| 409 | Конфликт (например, ссылка уже использована) |
| 422 | Ошибка валидации |
| 429 | Превышен лимит запросов |
| 500 | Внутренняя ошибка |
---
## 2. Аутентификация
### 2.1. `POST /auth/login-token` — выдать одноразовую ссылку (координатор)
**Права:** `coordinator`, `admin`.
```json
// request
{ "region_id": 12, "user_id": null, "ttl_minutes": 60 }
// response 201
{ "id": "uuid", "url": "https://opora.my-dpr.ru/login?token=...", "expires_at": "..." }
```
### 2.2. `POST /auth/login` — вход по одноразовой ссылке
```json
// request
{ "token": "...", "messenger_type": "telegram", "messenger_id": "123456" }
// response 200
{
"access_token": "jwt",
"refresh_token": "opaque",
"expires_at": "...",
"user": { "id": 1, "full_name": "..." }
}
```
**Ошибки:** `409 link_already_used` → «Ссылка уже была использована. Запросите новую у координатора».
### 2.3. `POST /auth/refresh` — обновить access-токен
```json
// request
{ "refresh_token": "opaque" }
// response 200
{ "access_token": "jwt", "refresh_token": "opaque", "expires_at": "..." }
```
**Правило:** refresh-токен ротируемый (при обновлении выдаётся новый, старый инвалидируется).
### 2.4. `POST /auth/logout` — завершить сессию
### 2.5. `GET /auth/me` — текущий пользователь
```json
{
"id": 1, "full_name": "Иванов И.И.", "member_type": "member",
"regions": [{ "id": 12, "name": "ДНР", "is_primary": true }],
"roles": [{ "code": "region_head", "region_id": 12 }]
}
```
---
## 3. Регионы и карточка
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/regions` | авторизован | список регионов (фильтр по округу) |
| GET | `/regions/{id}` | авторизован | регион |
| GET | `/regions/{id}/card` | авторизован | карточка региона |
| PATCH | `/regions/{id}/card` | region_head (своё), admin | обновить карточку |
| GET | `/regions/{id}/links` | авторизован | ссылки на сообщества/чаты |
| POST | `/regions/{id}/links` | region_head (своё), admin | добавить ссылку |
| PATCH | `/regions/{id}/links/{linkId}` | region_head (своё), admin | изменить |
| DELETE | `/regions/{id}/links/{linkId}` | region_head (своё), admin | удалить |
| GET | `/regions/{id}/members` | авторизован | члены региона |
| GET | `/regions/{id}/history` | авторизован | история изменений карточки |
**Пример `GET /regions/12/card`:**
```json
{
"region": { "id": 12, "name": "Донецкая Народная Республика", "district": "ЮФО" },
"population": 1200,
"members_count": 340,
"residents_count": 45,
"head": { "id": 5, "full_name": "..." },
"links": [{ "id": 1, "type": "chat", "title": "Чат ДНР", "url": "..." }],
"updated_at": "..."
}
```
---
## 4. Цели и метрики
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/goals` | авторизован | список целей |
| GET | `/goals/{id}` | авторизован | цель |
| GET | `/goals/{id}/metrics` | авторизован | метрики по цели (регионы/округа) |
| GET | `/metrics/regions` | авторизован | метрики по регионам |
| GET | `/metrics/districts` | авторизован | агрегаты по округам |
| GET | `/metrics/ratings` | авторизован | рейтинг регионов |
| GET | `/metrics/red-zone` | авторизован | регионы без прироста |
| POST | `/uploads` | admin | загрузить выгрузку |
| GET | `/uploads` | admin | список выгрузок |
**Пример `GET /goals/1/metrics`:**
```json
{
"goal": { "id": 1, "name": "Рост базы членов", "target_value": 1096 },
"point0": { "date": "2026-05-08", "value": 1000 },
"current": { "date": "2026-08-07", "value": 1338 },
"growth": 338,
"districts": [
{ "id": 1, "name": "ЮФО", "growth": 365, "regions_with_dynamics": 12, "regions_total": 12 }
],
"freshness_days": 3,
"auto_update": true
}
```
**Пример `GET /metrics/ratings?sort=-growth`:**
```json
{
"items": [
{ "region_id": 77, "name": "Москва", "growth": 224, "has_dynamics": true },
{ "region_id": 2, "name": "Башкортостан", "growth": 217, "has_dynamics": true }
],
"total": 89
}
```
---
## 5. Задачи (трекер)
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/tasks` | авторизован | список (фильтры: мои, регион, просроченные) |
| POST | `/tasks` | авторизован | создать |
| GET | `/tasks/{id}` | авторизован | задача |
| PATCH | `/tasks/{id}` | автор/исполнитель/admin | изменить (в т.ч. статус) |
| DELETE | `/tasks/{id}` | автор/admin | удалить (мягко) |
| GET | `/tasks/{id}/comments` | авторизован | комментарии |
| POST | `/tasks/{id}/comments` | авторизован | добавить комментарий |
**Пример `POST /tasks`:**
```json
// request
{
"title": "Подготовить отчёт по приросту",
"description": "...",
"due_date": "2026-10-01",
"priority": "high",
"region_id": 12,
"assignee_user_id": 5
}
// response 201
{ "id": 101, "status": "new", "created_at": "..." }
```
**Статусы:** `new → in_progress → review → done`.
---
## 6. Артефакты
| Метод | Путь | Права | Описание |
|---|---|---|---|
| POST | `/artifacts` | авторизован | загрузить файл (multipart) |
| GET | `/artifacts` | авторизован | список (фильтр по региону/цели/задаче) |
| GET | `/artifacts/{id}` | авторизован | метаданные |
| GET | `/artifacts/{id}/download` | авторизован | скачать (presigned URL) |
| DELETE | `/artifacts/{id}` | автор/admin | удалить |
**Ограничения:** до 50 МБ, типы: изображения, PDF, DOCX, XLSX (п. 4.2.8.1 ТЗ).
---
## 7. База знаний
| Метод | Путь | Права |
|---|---|---|
| GET | `/knowledge/articles` | авторизован |
| GET | `/knowledge/articles/{id}` | авторизован |
| GET | `/knowledge/categories` | авторизован |
---
## 8. Календарь событий
| Метод | Путь | Права |
|---|---|---|
| GET | `/events` | авторизован |
| GET | `/events/{id}` | авторизован |
| POST | `/events/{id}/reminders` | авторизован |
---
## 9. Запросы (заявки)
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/requests` | авторизован | список (свои / по региону / все для admin) |
| POST | `/requests` | авторизован | создать заявку |
| GET | `/requests/{id}` | авторизован | заявка |
| PATCH | `/requests/{id}` | admin/coordinator | изменить статус |
| POST | `/requests/{id}/comments` | авторизован | комментарий |
**Типы:** `join`, `access`, `event`, `support`.
---
## 10. Советы от регионов
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/advices` | авторизован | список (публичные) |
| POST | `/advices` | region_head (при наличии динамики) | опубликовать совет |
| PATCH | `/advices/{id}` | автор/admin | скрыть/изменить |
---
## 11. Рассылки (администрирование)
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/broadcasts` | admin | список |
| POST | `/broadcasts` | admin | создать (из шаблона) |
| GET | `/broadcasts/{id}` | admin | статус и прогресс |
| POST | `/broadcasts/{id}/send` | admin | запустить |
| GET | `/broadcast-templates` | admin | шаблоны |
| PATCH | `/broadcast-templates/{id}` | admin | редактировать текст |
**Пример `GET /broadcasts/5`:**
```json
{
"id": 5, "type": "rating", "status": "sending",
"total": 10000, "sent": 4200, "failed": 12,
"started_at": "...", "finished_at": null
}
```
---
## 12. История платформы
| Метод | Путь | Права |
|---|---|---|
| GET | `/history` | авторизован (публичная лента) |
---
## 13. Ассистент «Зам»
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/assistant/config` | владелец | настройки ассистента |
| PATCH | `/assistant/config` | владелец | включить/ограничить |
| GET | `/assistant/actions` | владелец | журнал действий ассистента |
**Пример `PATCH /assistant/config`:**
```json
{ "enabled": true, "permissions": { "publish": true, "delete": false } }
```
---
## 14. Администрирование
| Метод | Путь | Права | Описание |
|---|---|---|---|
| GET | `/users` | admin | список пользователей |
| PATCH | `/users/{id}` | admin | изменить (роль, статус, регион) |
| POST | `/users/{id}/roles` | admin | выдать роль |
| DELETE | `/users/{id}/roles/{roleId}` | admin | отозвать роль |
| GET | `/audit` | admin | журнал аудита |
---
## 15. Взаимодействие с ботом
Бот использует то же API (сервисный токен) для:
- выдачи одноразовых ссылок (`POST /auth/login-token`);
- подтверждения входа;
- отправки рассылок (через Celery, не напрямую);
- команд `/start`, `/login`, `/requests`, `/regions`, `/goals`, `/events`, `/help`.
**Внутренний webhook бота:** `POST /internal/bot/telegram` и `POST /internal/bot/max` (проверка подписи, только localhost).
---
## 16. Ограничения и лимиты
| Область | Лимит |
|---|---|
| Аутентификация | 10 запросов/мин на IP |
| Общие запросы | 100 запросов/мин на пользователя |
| Загрузка файлов | 50 МБ, 20 файлов/час |
| Рассылки | батчами, не более 30 сообщений/сек на канал |
---
## 17. Принятые решения и открытые вопросы
**Принято:**
1. **JWT** — access-токен сроком **1 месяц** + **refresh-токен** (ротируемый). Эндпоинт `POST /auth/refresh`.
- *Рекомендация по безопасности:* для продакшена желательно сократить access-токен (например, до 1 часа) и держать длинным refresh; при текущем решении компромисс — короткий срок хранения сессий и журналирование.
2. **Сервисный токен бота** — отдельный тип токена (системный актор), не роль пользователя.
3. **Вебхуки MAX** — проверяем после подтверждения возможностей MAX Bot API.
**Открыто:**
4. **Presigned URL** — срок жизни ссылок на скачивание артефактов (предложение: 15 минут).
---
## 18. Следующий документ
- Документ 3: макеты экранов (mobile-first).