Appearance
API-контракт Contour Node
Аудитория: разработчик самого Contour Node. Это спецификация того, что узел обязан предоставлять, чтобы студия NeuroCast могла с ним работать. Контракт выведен из реального HTTP-клиента студии —
HttpContourApiRepositoryи контроллера webhook'овContourWebhookController.
1. HTTP-эндпоинты узла (студия → узел)
Студия общается с узлом через HttpContourApiRepository. Узел обязан предоставлять перечисленные эндпоинты по адресу http://<networkIp>:<apiPort>. Базовый префикс — /api/v1.
| Метод | Путь | Назначение | Источник |
|---|---|---|---|
GET | /api/v1/health | health-check; ответ { status: "ok", minio: "ok" | "bucket_missing" } | HttpContourApiRepository.ts:317-345, CompanyService.ts:161-163 |
POST | /api/v1/upload/presigned | presigned-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/compressionPOST {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).