REST API
Структура эндпоинтов Garden, аутентификация, OpenAPI и интеграционные точки.
REST API
API живёт в Backend (Garden). Все эндпоинты находятся под префиксом /api/. Веб-фронт обращается к API через Axios SDK; SurveyJS lazy loading проксируется через Next.js API-роуты.
OpenAPI
Схема генерируется через drf-spectacular:
| URL | Что |
|---|---|
/api/schema/ | JSON-схема (OpenAPI 3.0) |
/api/schema/swagger-ui/ | Интерактивный Swagger |
/api/schema/redoc/ | ReDoc |
Аутентификация
Все эндпоинты (за исключением вебхуков и публичных страниц) требуют:
Authorization: Bearer <supabase_jwt>JWT валидирует класс SupabaseAuthentication в garden/modules/shared/auth/rest.py. По sub из токена находится User.supabase_uid. Дальше срабатывают per-view permissions.
Группы эндпоинтов
/api/auth/ модуль account
/api/workers/ модуль account (lazy для SurveyJS)
/api/service-visits/ модуль service_visit
/api/service-visit-members/ модуль service_visit
/api/services/ модуль service (справочник)
/api/service-prices/ модуль service
/api/service-visit-services/ модуль service (факт выполнения)
/api/materials/ модуль material (справочник)
/api/material-prices/ модуль material
/api/service-visit-materials/ модуль material (факт расхода)
/api/reports/ модуль report
/api/comms/telegram/ модуль communication
/api/webhooks/telegram/<delivery_id> модуль notification
/api/webhooks/resend/ модуль notification
/api/report-deliveries/<delivery_id>/webhook алиас (старый бот)
/api/notifications/unsubscribe/execute/ модуль notificationКлючевые эндпоинты
Авторизация и пользователи
| Метод | Путь | Назначение |
|---|---|---|
GET | /api/auth/supabase/invite-complete/ | Завершение приглашения после возврата из Supabase Auth |
GET | /api/workers/?search=...&take=10&skip=0&exclude=id1,id2 | Список работников для SurveyJS lazy loading |
Формат ответа /api/workers/:
{
"results": [
{ "value": "acc_2TCXd9h4...", "text": "Иванов Иван" },
{ "value": "acc_2TCXd9h5...", "text": "Петров Пётр" }
],
"count": 25
}Параметры (совместимы с SurveyJS choicesLazyLoadEnabled):
skip— offset (default: 0)take— limit (default: 50)text/q/search— поиск поfull_name(icontains)exclude— список ID через запятую (исключить уже выбранных)ids— список ID для batch-получения (для отображения сохранённых выборов)
Наряды
| Метод | Путь | Описание |
|---|---|---|
GET | /api/service-visits/ | Список с фильтрацией |
POST | /api/service-visits/ | Создать наряд |
GET | /api/service-visits/{id}/ | Детали |
PATCH | /api/service-visits/{id}/ | Обновить |
POST | /api/service-visits/{id}/started/ | planned → started + WORK_STARTED |
POST | /api/service-visits/{id}/completed/ | started → completed + WORK_COMPLETED |
POST | /api/service-visits/{id}/cancelled/ | → cancelled |
GET POST | /api/service-visits/{id}/members/ | Список / добавление участника |
DELETE | /api/service-visits/{id}/members/{member_id}/ | Удалить участника |
GET POST DELETE | /api/service-visit-members/ | CRUD участника |
Фильтры списка нарядов: status, object, brigadier, planned_from, planned_to, active. Поиск: по объекту, бригадиру, имени команды. Сортировка: planned_for, created_at, status, started_at, ended_at.
Отчёты
| Метод | Путь | Описание |
|---|---|---|
GET POST | /api/reports/ | Список / создание |
GET PATCH | /api/reports/{id}/ | Детали / обновление |
POST | /api/reports/{id}/survey/ | Сохранить черновик survey_data |
POST | /api/reports/{id}/survey/complete/ | `draft |
POST | /api/reports/{id}/survey/approve/ | submitted → approved (sync members) |
POST | /api/reports/{id}/survey/reject/ | submitted → rejected (требует rejection_comment) |
POST | /api/reports/{id}/survey/send-to-client/ | Отправить отчёт клиенту (REPORT_READY) |
GET | /api/reports/{id}/deliveries/ | История доставок |
GET | /api/reports/{id}/report-summary/ | Агрегированный отчёт для публичной страницы |
Уведомления и вебхуки
| Метод | Путь | Источник |
|---|---|---|
POST | /api/webhooks/telegram/<delivery_id> | Telegram-бот (HMAC-SHA256) |
POST | /api/webhooks/resend/ | Resend (Svix-подпись) |
POST | /api/report-deliveries/<delivery_id>/webhook | Старая сборка бота — алиас |
POST | /api/notifications/unsubscribe/execute/ | Внутренний — фронт переадресует токен |
Справочники
/api/materials/ # CRUD материалов
/api/material-prices/ # история цен
/api/service-visit-materials/ # факт расхода на наряде
/api/services/ # CRUD услуг
/api/service-prices/ # история цен на услуги
/api/service-visit-services/ # факт выполнения на нарядеFrontend SDK
В frontend/src/libs/sdk/ лежит сгенерированный TypeScript-клиент к API. Использование:
import { getServiceVisits, approveReport } from '@/libs/sdk';
const visits = await getServiceVisits({ status: 'planned' });
await approveReport(reportId);Регенерация SDK — после любого изменения API:
cd garden && python manage.py spectacular --file ../frontend/openapi.yaml
cd ../frontend && yarn generate:sdkПрокси через Next.js API
Для SurveyJS lazy loading фронт проксирует запросы через свои API-роуты:
| Next.js route | Проксирует в Django |
|---|---|
/api/survey/workers | /api/workers/ |
/api/survey/materials | /api/materials/ |
/api/survey/services | /api/services/ |
Это нужно, потому что SurveyJS не умеет передавать кастомные заголовки в lazy-load запросах — JWT добавляется в Next.js-роуте.
Pagination
Стандартная пагинация — StandardResultsSetPagination в garden/ng_metrics/pagination.py. Параметры: page (default 1), page_size (default из настроек). Формат ответа:
{
"count": 123,
"next": "http://api/...?page=2",
"previous": null,
"results": [...]
}Filtering и search
Используется django-filter (DjangoFilterBackend) + DRF SearchFilter + OrderingFilter. На каждом ViewSet задаются:
filterset_classилиfilterset_fields— точные совпадения и диапазоны.search_fields— какие поля участвуют в?search=....ordering_fields— какие поля можно сортировать через?ordering=....
Обработка ошибок
Стандартный DRF-ответ при ошибке валидации:
{
"field_name": ["Error message"],
"another_field": ["Another error"]
}Для нестандартных ошибок (например, неправильный переход статуса) — единичный ответ:
{ "detail": "Нельзя запустить выезд из текущего статуса." }Коды ошибок: 400 (bad request / валидация), 401 (нет JWT / истёк), 403 (нет прав), 404 (не найдено), 409 (конфликт состояний — редко).
Webhook payload
Telegram → Garden
POST /api/webhooks/telegram/<delivery_id>
Content-Type: application/json
X-Signature: <HMAC-SHA256 от тела>
{
"status": "sent" | "failed",
"error": "...", // только при failed
"message_id": 12345 // только при sent
}Resend → Garden
POST /api/webhooks/resend/
Content-Type: application/json
svix-id: ...
svix-timestamp: ...
svix-signature: ...
{
"type": "email.delivered", // или email.sent / email.opened / email.bounced / email.complained
"data": {
"email_id": "...",
"to": ["..."],
"subject": "...",
"headers": { "X-Delivery-Id": "rprtdel_..." }
}
}X-Delivery-Id — кастомный заголовок, который мы выставляем при отправке. По нему находим соответствующий ReportDelivery.
Тестирование API
В Garden используется pytest + pytest-django:
cd garden && make test
# или
pytest modules/notification/tests/ -vФикстуры — стандартные DRF + factory_boy для генерации тестовых сущностей.