Corp/docs/guides/API.md

228 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` вручную.
## Task board templates
Authenticated employees can save the *structure* of an accessible, non-archived
task board as a personal template. Staff and superusers can also publish templates
into the company-wide library. Templates contain active columns, nested columns
and their rows (including names, colors and positions); they do not contain tasks,
members, attachments, comments, automations or board permissions.
- `GET /api/v1/tasks/board-templates/` — list the current user's personal templates
and all company-wide templates.
- `POST /api/v1/tasks/board-templates/` — create a snapshot from an accessible
board. Example: `{"name":"Sprint","source_board_id":42,"is_shared":false}`.
Setting `is_shared: true` requires staff or superuser privileges.
- `PATCH /api/v1/tasks/board-templates/{id}/` — rename or change the description
of an owned template; only administrators can mark it as shared.
- `DELETE /api/v1/tasks/board-templates/{id}/` — delete an owned template.
- `POST /api/v1/tasks/board-templates/{id}/instantiate/` — create a new board
from any visible template. Optional JSON: `{"name":"Sprint 24"}`.
The new board always belongs to the caller and starts with `access_scope:
"private"`, no tasks, and no members. The caller may subsequently edit its
access using the normal board update endpoint.
Templates are independent snapshots: changes to a source board do not
retroactively modify stored templates or boards created from them. Changing the
template's metadata does not change its saved structure.