# 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. ## Task checklist templates The checklist template library is separate from board layout templates. An authenticated employee can create personal templates from one or more checklist item titles. Staff and superusers can publish shared templates visible to every employee. Only the author can update or delete a template. - `GET /api/v1/tasks/checklist-templates/` — list personal and shared templates (standard paginated DRF response). - `POST /api/v1/tasks/checklist-templates/` — create a template from `{"name":"Quality check","items":["Inspect","Sign off"],"is_shared":false}`; alternatively supply `source_task_id` instead of `items` to save a snapshot of an accessible task's checklist. - `PATCH /api/v1/tasks/checklist-templates/{id}/` — edit an owned template. - `DELETE /api/v1/tasks/checklist-templates/{id}/` — delete an owned template. - `POST /api/v1/tasks/checklist-templates/{id}/apply/` with `{"task_id":42}` — append a template's items to an accessible task. Titles are normalized and limited to 500 characters each. A template has 1–100 items; its snapshot does **not** include completion state or actors. Applying it adds uncompleted items to the end of the task checklist, transactionally, leaving existing items untouched. Applying it to a draft task from the frontend form adds draft items; persistence happens when the task is saved. ### Three-level checklist template publication The checklist template library has a `scope`: `personal`, `board` or `company`. Legacy `is_shared` remains in API responses for compatibility and is true only for company-level templates. `board_id` is set only for `board` scope. The user's board groups have no bearing on permissions. Board templates inherit the board's `private`, `restricted` and `all` visibility rules. Their creators and explicitly appointed editors, **not all board members**, may create/edit/archive board templates. Company publication is available to staff, superusers or users with the `tasks.manage_company_checklist_templates` permission. Company template administrators do not automatically gain access to edit other boards' tasks. - `POST /api/v1/tasks/checklist-templates/` supports `{"scope":"board","board_id":42,"name":"Сборка","items":["Этап 1"]}` only for a board creator or its appointed template editor. - `POST /api/v1/tasks/checklist-templates/{id}/propose/` with `{"board_id":42}` snapshots an author's personal template into a board proposal without changing that personal template. - `GET /api/v1/tasks/checklist-template-proposals/?board_id=42` lists proposals visible to their author or to that board's managers. - `POST /api/v1/tasks/checklist-template-proposals/{id}/review/` with `{"decision":"approved"}` or `{"decision":"rejected"}` is restricted to board managers. Approving creates a distinct, board-owned template. - `GET /api/v1/tasks/boards/{board_id}/checklist-template-editors/` returns the currently appointed employee IDs and whether the caller can manage templates. The board creator can send `POST` with `{"user_id":123,"enabled":true}` to appoint an active employee with access to that board, or `false` to revoke access. - `GET /api/v1/tasks/checklist-templates/{id}/versions/` returns immutable content snapshots and authors of revisions. Updating a template bumps `version` but does not mutate any previously applied checklist. - `DELETE /api/v1/tasks/checklist-templates/{id}/` archives the template instead of destroying its history. A board template can only be applied to tasks on the same board. Personal and company templates may be applied to tasks on any board to which the caller has task access. Company publication is a separate privilege from board template editing. Proposing the same personal template to multiple boards creates separately approved, independently versioned board copies; a single approval never publishes it to the whole company. Existing templates with `is_shared=true` are automatically migrated to company scope with their content preserved. Authors use protected foreign keys and do not vanish when their account is deactivated. Company/board templates survive staff departure. Pending proposals remain snapshots even if the original personal template changes or is archived. ## Quick project assignment for procurement requests A procurement request may reference zero, one, or several existing company projects. For buying staff, the API provides a **project-only action** independent from editing the whole request: - `GET /api/v1/procurement/requests/{id}/project-options/` — compact `[{"id":1,"name":"Project"}]` directory of existing projects, available only to users allowed to assign projects on this request. - `PATCH /api/v1/procurement/requests/{id}/projects/` with `{"projects":[1,2]}` — replace project associations, including on approved/completed requests; `{"projects":[]}` clears them. Other request fields are not mutated by this endpoint. Repeating the same associations is idempotent and does not create duplicate history entries. - `GET /api/v1/procurement/requests/?project=unassigned` — find requests with no project. This works together with scope, status and period filters. Access is granted to staff/superusers, users with the explicit Django model permission `procurement.assign_procurement_projects`, users with the existing `procurement.execute_procurement` permission, or the current request executor. Regular applicants do not acquire unrelated editing permissions. Assignment audit records actor, time, previous project IDs/names and new project IDs/names in `ProcurementRequestActivity`. Edits through the normal request PATCH endpoint also record project-association changes. The frontend presents this before/after information in the request history. The create/edit form shows a soft reminder when a project has not yet been selected, but still allows saving a request without it.