371 lines
14 KiB
Markdown
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®ion_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).
|