diff --git a/docs/tech-design/02-api.md b/docs/tech-design/02-api.md new file mode 100644 index 0000000..6382192 --- /dev/null +++ b/docs/tech-design/02-api.md @@ -0,0 +1,348 @@ +# Технический проект. Документ 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 ` (сессия после входа через бота). +- **Версионирование:** префикс `/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", "expires_at": "...", "user": { "id": 1, "full_name": "..." } } +``` + +**Ошибки:** `409 link_already_used` → «Ссылка уже была использована. Запросите новую у координатора». + +### 2.3. `POST /auth/logout` — завершить сессию + +### 2.4. `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-токена и нужен ли refresh. +2. **Сервисный токен бота** — отдельный тип или роль `system`. +3. **Presigned URL** — срок жизни ссылок на скачивание артефактов. +4. **Вебхуки MAX** — уточнить после проверки MAX Bot API. + +--- + +## 18. Следующий документ + +- Документ 3: макеты экранов (mobile-first).