202 lines
12 KiB
Markdown
202 lines
12 KiB
Markdown
# HTTP API
|
||
|
||
Этот документ описывает текущий HTTP API EUSRR. Полный список операций и схем ответов генерируется из кода; вручную поддерживаемый список endpoint-ов здесь намеренно не дублируется.
|
||
|
||
## Источники истины
|
||
|
||
- URL верхнего уровня: `backend/api/urls.py`
|
||
- API v1: `backend/api/v1/urls.py`
|
||
- authentication: `backend/api/auth/`
|
||
- OpenAPI tags и metadata: `backend/api/schema.py`
|
||
- frontend transport: `frontend/src/lib/api/client.ts`
|
||
- доменные frontend clients: `frontend/src/lib/api/`
|
||
- проверка документации: `backend/tests/api/test_documentation.py`
|
||
|
||
Если этот guide расходится с `/api/schema/`, верной считается OpenAPI-схема.
|
||
|
||
## Локальный запуск
|
||
|
||
Backend запускается как ASGI-приложение. Один процесс обслуживает HTTP и WebSocket:
|
||
|
||
```bash
|
||
cd backend
|
||
python manage.py migrate
|
||
daphne -b 0.0.0.0 -p 8000 eusrr_backend.asgi:application
|
||
```
|
||
|
||
Frontend:
|
||
|
||
```bash
|
||
cd frontend
|
||
cp .env.example .env.local
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
Для стандартной локальной конфигурации:
|
||
|
||
```dotenv
|
||
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
|
||
NEXT_PUBLIC_WS_URL=ws://localhost:8000/ws/
|
||
```
|
||
|
||
## Документация, доступная из приложения
|
||
|
||
| URL | Назначение |
|
||
| --- | --- |
|
||
| `/api/schema/` | OpenAPI 3 schema |
|
||
| `/api/docs/` | Swagger UI |
|
||
| `/api/redoc/` | ReDoc |
|
||
| `/api/ws/schema/` | AsyncAPI schema для `/ws/` |
|
||
| `/api/ws/docs/` | HTML-страница WebSocket API |
|
||
|
||
Эти страницы доступны без аутентификации. Бизнес-endpoint-ы по умолчанию требуют авторизованного пользователя и Django model permissions.
|
||
|
||
## Пространства URL
|
||
|
||
| Prefix | Содержимое |
|
||
| --- | --- |
|
||
| `/api/auth/` | JWT, QR login, регистрация, password reset, auth-сессии |
|
||
| `/api/v1/` | Основной versioned REST API |
|
||
| `/api/v1/auth/` | Runtime alias authentication API для старых клиентов; в OpenAPI не дублируется |
|
||
| `/api/v1/communications/` | Чаты, сообщения и опросы |
|
||
| `/api/v1/notifications/` | Уведомления, preferences и Web Push |
|
||
| `/api/v1/schedule/` | Календари, события, правила, occurrences и relations |
|
||
| `/api/v1/procurement/` | Закупки и оборудование |
|
||
| `/api/v1/finance/` | Финансы и payroll |
|
||
| `/api/v1/tasks/` | Задачи, доски и automation |
|
||
|
||
Версии `/api/v2/` в текущем проекте нет. Не добавляйте ссылки на неё и не используйте старые `VITE_API_URL`/Vite-примеры: frontend построен на Next.js.
|
||
|
||
## Аутентификация
|
||
|
||
Основной способ — JWT:
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/api/auth/token/ \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"email":"user@example.com","password":"change-me"}'
|
||
```
|
||
|
||
Вместо `email` можно передать `phone` или `phone_number`. Успешный ответ содержит `access` и `refresh`.
|
||
|
||
Защищённый запрос:
|
||
|
||
```bash
|
||
curl http://localhost:8000/api/v1/employees/me/ \
|
||
-H 'Authorization: Bearer ACCESS_TOKEN'
|
||
```
|
||
|
||
Обновление access token:
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/api/auth/token/refresh/ \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"refresh":"REFRESH_TOKEN"}'
|
||
```
|
||
|
||
Каждая пара токенов привязана к записи `UserAuthSession` через claim `session_id`. Удалённая или отозванная сессия инвалидирует и HTTP, и WebSocket-доступ. Управление сессиями:
|
||
|
||
- `GET /api/auth/sessions/`
|
||
- `DELETE /api/auth/sessions/{session_id}/`
|
||
- `POST /api/auth/sessions/logout-others/`
|
||
|
||
Frontend хранит access/refresh token в `localStorage`, добавляет Bearer header, выполняет единичный refresh при `401` и повторяет исходный запрос. Ошибка refresh очищает локальную сессию.
|
||
|
||
Новые пароли проходят стандартные Django validators: минимум 8 символов, запрет полностью цифровых и общеупотребительных паролей, проверка сходства с данными пользователя там, где пользователь известен. LDAP/AD может применять более строгую доменную политику дополнительно.
|
||
|
||
DRF ограничивает анонимные запросы до `60/min`, авторизованные — до `600/min`. Для login действует отдельный предел `10/min`, для registration и email/reset операций — `5/hour`, refresh — `60/min`. Значения задаются в `REST_FRAMEWORK.DEFAULT_THROTTLE_RATES`.
|
||
|
||
## Формат запросов и ответов
|
||
|
||
- JSON используется по умолчанию.
|
||
- Для загрузки файлов передавайте `FormData`; API client не устанавливает `Content-Type` вручную, чтобы браузер добавил multipart boundary.
|
||
- Стандартная пагинация: `page` и `page_size`, значение по умолчанию — 20.
|
||
- Максимальный `page_size` базового paginator — 200. Клиенты, которым нужен полный справочник, обходят страницы по полю `next`, а не запрашивают неограниченную выдачу.
|
||
- Ручная выдача notifications ограничена 100 элементами; message history и messages-around — суммарно 100 сообщениями за запрос.
|
||
- `204 No Content` не содержит JSON-тело.
|
||
- `401` означает отсутствующую, истёкшую или отозванную auth-сессию.
|
||
- `403` означает, что пользователь распознан, но не имеет требуемого model/object permission.
|
||
- `400` обычно содержит field-level validation errors; не преобразуйте его в общий `500` на frontend.
|
||
|
||
Глобальный поиск не расширяет права пользователя: результаты чатов, сообщений, документов, календарных событий, заявлений, закупок и уведомлений повторно проверяются по object-level правилам соответствующего домена.
|
||
|
||
## Frontend proxy
|
||
|
||
Browser вызывает относительные пути `/api/...`. Next.js route `frontend/src/app/api/[...path]/route.ts` проксирует их на `${NEXT_PUBLIC_BACKEND_URL}/api/...` и сохраняет query string, authorization headers, multipart body и streaming GET responses.
|
||
|
||
Используйте собранный client:
|
||
|
||
```ts
|
||
import { apiClient } from "@/lib/api";
|
||
|
||
const currentUser = await apiClient.getCurrentUser();
|
||
```
|
||
|
||
Новый доменный API добавляется отдельным модулем в `frontend/src/lib/api/` и подключается в `frontend/src/lib/api/index.ts`. Не создавайте второй fetch-wrapper и не встраивайте абсолютный backend URL в компоненты.
|
||
|
||
## Communications API
|
||
|
||
Router публикует:
|
||
|
||
- `communications/chats`
|
||
- `communications/messages`
|
||
- `communications/polls`
|
||
|
||
Кроме CRUD доступны actions для участников, ролей, поиска, истории, отметки прочитанного, загрузок, реакций, пересылки, bulk delete и голосований. Их точные URL и payload смотрите в Swagger: они формируются из `backend/communications/api/viewsets.py`.
|
||
|
||
### Упоминания в сообщениях
|
||
|
||
Доступные для `@` участники запрашиваются с учётом object-level доступа к чату:
|
||
|
||
```text
|
||
GET /api/v1/communications/chats/{chat_id}/mention-candidates/?q=Ива
|
||
```
|
||
|
||
Ответ содержит `results` с полями `id`, `name`, `email`, `avatar`. Автор запроса исключён; в личном чате возвращается только собеседник, в групповом — активные участники, в глобальном — активные пользователи. Параметр `q` ищет каждый введённый фрагмент по фамилии, имени, отчеству, email и LDAP-логину, поэтому составные ФИО можно вводить в любом порядке.
|
||
|
||
При создании multipart-сообщения или `PATCH` передавайте `mentioned_user_ids` — JSON-массив выбранных ID. Сервер повторно проверяет доступ участников и наличие видимого `@Полное имя` в `content`; подставить посторонний ID нельзя. В ответе сообщения поле `mentions` содержит проверенные `{user_id, name, start, end}` для безопасного выделения текста. Максимум — 50 выбранных пользователей на сообщение.
|
||
|
||
Список и detail чата возвращают `has_unread_mention`. Значение остаётся `true` после перезагрузки и сбрасывается вместе с read marker; frontend показывает его отдельным значком `@` на аватаре чата. Упоминание создаёт `chat_mention` даже при выключенных уведомлениях конкретного чата, не создавая второе обычное уведомление.
|
||
|
||
Для отметки прочитанного используйте HTTP action:
|
||
|
||
```text
|
||
POST /api/v1/communications/chats/{chat_id}/mark-read/
|
||
```
|
||
|
||
WebSocket action `mark_read` оставлен только как deprecated compatibility path и не выполняет запись.
|
||
|
||
## Добавление endpoint-а
|
||
|
||
1. Добавьте serializer, permissions и view/viewset в доменный модуль.
|
||
2. Зарегистрируйте URL под `/api/v1/`; unversioned бизнес-URL не добавляются.
|
||
3. Добавьте `extend_schema` там, где drf-spectacular не может вывести контракт автоматически.
|
||
4. Проверьте, что object permissions фильтруют и list, и detail queryset.
|
||
5. Добавьте backend tests для успешного запроса, `401`, `403`, validation и ownership boundaries.
|
||
6. Добавьте или расширьте доменный frontend client.
|
||
7. Убедитесь, что endpoint появился в `/api/schema/` с правильным tag.
|
||
|
||
## Проверка
|
||
|
||
```bash
|
||
cd backend
|
||
pytest tests/api/test_documentation.py tests/api/auth tests/api/v1
|
||
```
|
||
|
||
Точечные suites предпочтительнее ручной коллекции curl-команд. Для диагностики контракта:
|
||
|
||
```bash
|
||
curl -fsS http://localhost:8000/api/schema/ -H 'Accept: application/json'
|
||
```
|
||
|
||
При изменении WebSocket-протокола дополнительно обновите `backend/docs/architecture/websocket-asyncapi.yaml` и выполните realtime tests.
|
||
|
||
## Типовые ошибки
|
||
|
||
- `404` на `/api/v2/...`: такой версии нет; используйте `/api/v1/...`.
|
||
- `401` после успешного login: проверьте, что отправляется access token, а auth-сессия не отозвана.
|
||
- Повторяющиеся refresh-запросы: используйте общий `apiClient`, в нём refresh уже сериализован.
|
||
- CORS в локальной разработке: browser должен обращаться к Next.js `/api/...`; прямой backend-доступ разрешён только origin-ам из `CORS_ALLOWED_ORIGINS`.
|
||
- Ошибка загрузки файла: не задавайте multipart `Content-Type` вручную.
|