Skip to content

API-контракт Contour Node

Аудитория: разработчик самого Contour Node. Это спецификация того, что узел обязан предоставлять, чтобы студия NeuroCast могла с ним работать. Контракт выведен из реального HTTP-клиента студии — HttpContourApiRepository и контроллера webhook'ов ContourWebhookController.

1. HTTP-эндпоинты узла (студия → узел)

Студия общается с узлом через HttpContourApiRepository. Узел обязан предоставлять перечисленные эндпоинты по адресу http://<networkIp>:<apiPort>. Базовый префикс — /api/v1.

МетодПутьНазначениеИсточник
GET/api/v1/healthhealth-check; ответ { status: "ok", minio: "ok" | "bucket_missing" }HttpContourApiRepository.ts:317-345, CompanyService.ts:161-163
POST/api/v1/upload/presignedpresigned-URL на загрузку. Тело: file_id, file_name, file_size, content_type, project_id. Ответ: upload_url, object_name:46-97
POST/api/v1/upload/confirmподтверждение загрузки + запуск сжатия. Тело: file_id, project_id, object_name, callback_url, file_type, compress, [compression_config], [audio_layout]. Ответ: task_id / compression_task_id:99-185
POST/api/v1/tasks/watermarkзадача водяного знака (опц. compress-then-watermark). Тело: file_id, project_id, object_name, executor_id, watermark{...}, callback_url, [compression_config], [logo_object_name]:187-255
GET/api/v1/tasks/{taskId}статус задачи:257-285
GET/api/v1/download/presigned?object_name=presigned-URL на скачивание. Ответ: download_url:287-315
POST/api/v1/upload/bucketпрямая загрузка буфера (multipart: file, object_name, bucket_name) — для логотипов watermark:347-387
GET/api/v1/download/bucket?object_name=&bucket_name=прямое скачивание объекта:389-423
DELETE/api/v1/delete/bucket?object_name=&bucket_name=удаление объекта:425-455

Все presigned-ссылки узел формирует, указывая в них свой MinIO-эндпоинтhttp://<networkIp>:<minioPort>/.... Студия при необходимости переписывает их на публичный прокси — см. MinIO reverse-proxy.

Важные детали по полям

  • /api/v1/upload/confirm: при compress=true узел обязан вернуть task_id или compression_task_id, иначе студия бросит ошибку «Contour не вернул идентификатор задачи сжатия» (HttpContourApiRepository.ts:155-168). При compress=false идентификатор не требуется — студия подставит 'confirm' (:146-153).
  • /api/v1/tasks/watermark: объект watermark содержит как минимум text, full_name, neurocast_task_id, position, opacity (:206-212). Поле logo_object_name приходит, только если у компании задан логотип; compression_config — только для пайплайна compress-then-watermark.
  • compression_config: { video_bitrate, [width], [height], [audio_layout] } (:217-221).

2. Webhook-колбэки (узел → студия)

После завершения задачи Contour Node обязан вызвать колбэк на адрес, который студия передала в поле callback_url. Студия строит его как ${APP_BASE_URL}/api/contour/webhook/{compression|watermark}.

  • POST {APP_BASE_URL}/api/contour/webhook/compression
  • POST {APP_BASE_URL}/api/contour/webhook/watermark

Формат тела:

json
{
  "task_id": "string",                       // обязательно
  "task_type": "compression" | "watermark",  // обязательно, должен совпадать с эндпоинтом
  "status": "completed" | "failed",          // обязательно
  "file_id": "string",                       // опц.
  "project_id": "string",                    // опц.
  "executor_id": "string | null",            // опц.
  "result_object_name": "string | null",     // опц. — имя объекта результата
  "error": "string | null",                  // опц. — текст ошибки при status=failed
  "completed_at": "string"                   // опц. — ISO-время
}

⚠️ task_type в теле должен совпадать с эндпоинтом, иначе 400 Bad Request (ContourWebhookController.ts:128-130, 154-156).

Безопасность колбэков

Эндпоинты публичные (без JWT), но защищены проверкой IP-источника: запрос принимается только если IP отправителя зарегистрирован как networkIp какого-либо узла (findByNetworkIp), иначе — 403 Forbidden. Локальные/Docker-адреса (127.0.0.1, ::1, 192.168.*, 172.*) пропускаются как dev-режим..

Поэтому Tailscale-IP узла должен совпадать с тем, что введён при регистрации (networkIp), и должен корректно доходить до backend без подмены. Если перед backend стоит обратный прокси — он обязан прокидывать реальный IP в x-forwarded-for (extractClientIp, ContourWebhookController.ts:103-111).