Tenant

Медиа и загрузки

Загрузка файлов (видео, изображения, PDF, документы) через presigned-URL в MinIO. Поток состоит из трёх шагов: 1) запрос presigned-URL, 2) прямая загрузка файла клиентом по полученному URL, 3) подтверждение загрузки. Доступно любому авторизованному участнику школы.

POST/api/v1/t/media/presign

Запрос presigned-URL

Tenant JWT

Шаг 1 из 3. Регистрирует медиа-актив в статусе pending и возвращает временный URL для прямой загрузки файла в MinIO. Поле filename используется только для отображения и НИКОГДА не участвует в построении ключа объекта. Заявленные declared_size и declared_mime фиксируются, но не считаются доверенными — реальный размер и MIME проверяются на шаге подтверждения.

Идентификатор загрузившего (uploader_id) берётся из JWT, а не из тела запроса. Размер ограничен лимитами на каждый тип файла; общий объём ограничен квотой хранилища школы.

Тело запроса

ПараметрТипОписание
kindобяз.stringТип файла. Допустимые значения: video, image, pdf, document.Пример: video
filenameобяз.stringОтображаемое имя файла (1–255 символов). Не используется в ключе хранения.Пример: lecture-01.mp4
declared_sizeобяз.integerЗаявленный размер файла в байтах (минимум 1). MinIO дополнительно ограничивает Content-Length при загрузке.Пример: 10485760
declared_mimeобяз.stringЗаявленный MIME-тип (3–127 символов). Записывается, но не считается доверенным.Пример: video/mp4

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/media/presign \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "video",
    "filename": "lecture-01.mp4",
    "declared_size": 10485760,
    "declared_mime": "video/mp4"
  }'

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "media_asset_id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "upload_url": "https://minio.edumentor.kz/lms-media/...",
    "form_data": {},
    "object_key": "acme/video/8f3b1c2a-....mp4",
    "expires_in": 900
  }
}

Ошибки

КодerrorКогда
400invalid_request_bodyТело запроса не прошло валидацию (например, недопустимый kind или нулевой размер).
400invalid_kindТип файла не входит в список допустимых.
400oversize_capЗаявленный размер превышает лимит для данного типа файла.
400storage_quota_exceededПревышена квота хранилища школы.
401unauthorizedТокен отсутствует, истёк или недействителен.
404tenant_not_foundШкола не найдена.
POST/api/v1/t/media/:id/confirm

Подтверждение загрузки

Tenant JWT

Шаг 3 из 3. Вызывается после успешной загрузки файла по presigned-URL. Сервер делает HEAD-запрос к объекту, повторно проверяет реальный размер и MIME-тип, и переводит актив в статус confirmed. Тело запроса пустое.

Поля duration_seconds (для видео) и total_pages (для pdf) извлекаются бэкендом асинхронно через воркер уже после подтверждения, поэтому сразу в ответе на confirm они обычно null. Они становятся доступны при последующем чтении актива (GET /api/v1/t/media/:id). Для типов, к которым метаданные неприменимы, значения остаются null.

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор медиа-актива, полученный на шаге presign.

Пример запроса

cURL
curl -X POST https://api.edumentor.kz/api/v1/t/media/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d/confirm \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "kind": "video",
    "filename": "lecture-01.mp4",
    "size": 10485760,
    "mime": "video/mp4",
    "status": "confirmed",
    "duration_seconds": null,
    "total_pages": null,
    "presigned_url": "https://minio.edumentor.kz/lms-media/...",
    "expires_in": 900
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatИдентификатор актива не является валидным UUID.
400confirm_failedНе удалось подтвердить загрузку (объект отсутствует в хранилище).
400oversize_capРеальный размер файла превышает лимит для типа.
400mime_mismatchРеальный MIME-тип файла не соответствует заявленному типу.
401unauthorizedТокен отсутствует, истёк или недействителен.
404media_not_foundМедиа-актив не найден в текущей школе.
GET/api/v1/t/media/:id

Получить медиа-актив

Tenant JWT

Возвращает метаданные актива и свежий presigned-URL для скачивания, если статус confirmed. Для активов в статусе pending или failed поле presigned_url пустое, а ответ возвращается с HTTP 409 и кодом media_not_confirmed (метаданные при этом включены в поле data).

Поля duration_seconds (длительность для видео) и total_pages (число страниц для pdf) авто-извлекаются бэкендом асинхронно через воркер после подтверждения загрузки. Они равны null, пока метаданные ещё не извлечены или неприменимы к типу актива (например, duration_seconds всегда null для pdf, а total_pages — для видео).

Параметры пути

ПараметрТипОписание
idобяз.uuidИдентификатор медиа-актива.

Пример запроса

cURL
curl https://api.edumentor.kz/api/v1/t/media/8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-Subdomain: acme"

Пример ответа

200 OK · application/json
{
  "success": true,
  "data": {
    "id": "8f3b1c2a-1d2e-4a5b-9c6d-7e8f9a0b1c2d",
    "kind": "video",
    "filename": "lecture-01.mp4",
    "size": 10485760,
    "mime": "video/mp4",
    "status": "confirmed",
    "duration_seconds": 360,
    "total_pages": null,
    "presigned_url": "https://minio.edumentor.kz/lms-media/...",
    "expires_in": 900
  }
}

Ошибки

КодerrorКогда
400invalid_id_formatИдентификатор актива не является валидным UUID.
401unauthorizedТокен отсутствует, истёк или недействителен.
404media_not_foundМедиа-актив не найден в текущей школе.
409media_not_confirmedАктив ещё не подтверждён (статус pending/failed); presigned-URL недоступен.