KNOTTA research & development

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": [...]
}

Используется 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 для генерации тестовых сущностей.

На странице