# Технический проект. Документ 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).