Перейти к основному содержимому

GET /api/messenger/recordings/{recordingId}/download — Ссылка на скачивание записи

Выдаёт участнику сессии короткоживущую ссылку на бинарь готовой записи (call-recording.md). Бинарь хранится во внешнем file storage (S3), в messenger — только FileId + метаданные (INV-REC-6); ссылка — пресайн-URL с ограниченным TTL, выдаётся по запросу и не сохраняется. Это чувствительное чтение (Audit = ReadSensitive).

Назначение

Дать участнику ссылку, по которой он скачает файл записи напрямую из file storage. Метод существует отдельно от get-recording.md, потому что выдача ссылки — чувствительное действие (аудит) и ссылка короткоживущая. file storage не публикует событий — этот метод и есть то, как «запись готова» становится действием: событие call-recording-available уведомляет, а ссылку клиент берёт здесь. Owning-сервис — messenger, feature group recordings.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API arearecordings
Feature grouprecordings
Статусdraft
Документdocs/06-services/messenger/api/recordings/get-recording-download.md

Актор и доступ

ПроверкаЗначение
Actor typeUser — участник сессии-владельца записи
Auth policy / Permissionspermission messenger.recordings.view
Scope (RBAC)не применяется (доступ — по участию в сессии)
Record-levelDirect: actor.UserId ∈ {CallerUserId, CalleeUserId} звонка; Conference: активное членство в ChatId конференции (INV-CHAT-4)
Tenant isolationнет (кросс-tenant); изоляция — по участию/членству
AuditReadSensitive — выдача ссылки на бинарь записи (кто, какую запись, когда); URL в аудит/лог не пишется

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/messenger/recordings/{recordingId}/download
Success status200 (OK)
Idempotency headerне применяется (чтение; каждый вызов выдаёт свежий пресайн)
Correlationсквозной trace через traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
RecordingIdrouteGuidДа!= Empty; существующая запись в Available, актор — участник сессии (шаги 3–5)
ActorUserIdidentity claimsGuidДаиз JWT; участник сессии-владельца

Модель ответа RecordingDownloadModel

Короткоживущий дескриптор скачивания. UrlSensitive: пресайн-URL с ограниченным TTL; исключён из логов/трейсов; клиент использует его сразу.

ПолеТипОбязательностьИсточникMaskingОписание
RecordingIdGuidДаCallRecordingEntity.IdИдентификатор записи.
FileIdGuidДа.FileIdСтабильная ссылка на бинарь в file storage.
UrlstringДаfile storage (пресайн)exclude из логовКороткоживущий пресайн-URL скачивания (TTL ~1 ч).
ExpiresAtDateTimeOffsetДаcomputed (пресайн TTL)Истечение ссылки; после — запросить заново.
FormatRecordingFormatДа.FormatКонтейнер (Mp4/Webm/Mkv/Mp3).
SizeByteslongДа.SizeBytesРазмер файла (для прогресса/проверки).

Алгоритм

  1. Контекст и доступ. Операцию выполняет участник сессии-владельца (User); ActorUserId — из JWT; требуется право messenger.recordings.view. Запись включена флагом RECORDINGS_ENABLED, иначе MSG_RECORDINGS_DISABLED.
  2. Проверка входа. RecordingId непустой; иначе ошибка валидации.
  3. Загрузка записи. Запись по RecordingId; нет → MSG_RECORDING_NOT_FOUND. Читаем SessionKind, ссылку на сессию, Status, FileId.
  4. Право (по типу сессии). Direct — актор ∈ {Caller, Callee}; Conference — актор активный член ChatId (INV-CHAT-4). Иначе → MSG_RECORDING_FORBIDDEN (либо MSG_RECORDING_NOT_FOUND для сокрытия).
  5. Готовность. Требуется Status == Available (INV-REC-2): иначе MSG_RECORDING_NOT_READY (ещё Pending/Recording/Uploading) — клиент повторит после события call-recording-available. При Deleted/Failed (FileId пуст) → MSG_RECORDING_FILE_MISSING.
  6. Пресайн. На FileId у file storage запрашивается короткоживущий пресайн-GET-URL (TTL ~1 ч); сбой/ недоступность file storage → MSG_STORAGE_UNAVAILABLE (клиент повторит).
  7. Результат. RecordingDownloadModel (Url Sensitive) → HTTP 200 OK; факт выдачи ссылки пишется в аудит ReadSensitive (без URL). Записи в БД метод не меняет.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone — метод чтения, Kafka не публикует
External callsfile storage (S3): пресайн-GET на FileId, TTL ~1 ч; сбой → MSG_STORAGE_UNAVAILABLE (retry)
Cache / projections / searchnone — состояние записи не меняется
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400RecordingId = Empty«Проверьте параметры запроса»no retry
MSG_RECORDING_FILE_MISSING400FileId пуст (запись Deleted/без файла)«Файл записи недоступен»no retry
MSG_RECORDING_NOT_FOUND404записи нет или актор не участник (сокрытие)«Запись не найдена»no retry
MSG_RECORDING_FORBIDDEN403актор не участник сессии-владельца«Недостаточно прав»no retry
MSG_RECORDING_NOT_READY409Status != Available (ещё Pending/Recording/Uploading)«Запись ещё готовится»retry после события готовности
MSG_STORAGE_UNAVAILABLE503file storage не выдал пресайн«Хранилище временно недоступно»retry позже
MSG_RECORDINGS_DISABLED503RECORDINGS_ENABLED = false«Записи временно недоступны»retry позже

Acceptance Criteria

  • Скачивание готовой записи. Дано запись в Available и актор — участник сессии-владельца (Direct: ∈ {Caller, Callee}; Conference: активный член ChatId). Когда GET. Тогда file storage выдаёт короткоживущий presign-GET-URL (TTL ~1 ч), факт выдачи пишется в аудит ReadSensitive (без URL), ответ 200 c RecordingDownloadModel; состояние записи в БД не меняется.
  • Ещё не готова. Дано запись в Pending/Recording/Uploading. Когда GET. Тогда MSG_RECORDING_NOT_READY (409) — клиент повторит после события call-recording-available.
  • Не участник. Дано актор не участник сессии-владельца. Когда GET. Тогда MSG_RECORDING_FORBIDDEN (403) либо MSG_RECORDING_NOT_FOUND (404) для сокрытия существования записи.
  • Файл отсутствует. Дано запись Deleted/Failed (пустой FileId). Когда GET. Тогда MSG_RECORDING_FILE_MISSING (400).
  • Хранилище недоступно. Дано file storage не выдал presign. Когда GET. Тогда MSG_STORAGE_UNAVAILABLE (503) с retry.
  • Записи выключены. Дано RECORDINGS_ENABLED = false. Когда GET. Тогда MSG_RECORDINGS_DISABLED (503).
  • Свежесть ссылки. Дано истёкший presign. Когда клиент повторяет запрос. Тогда выдаётся новая ссылка — каждый вызов генерирует свежий presign, ответ не кэшируется и не идемпотентен.
  • Приватность. Url/presign/бинарь не пишутся в логи/трейсы/аудит — фиксируется только факт выдачи (RecordingId, участник, ExpiresAt).

Совместимость и наблюдаемость

  • Новые optional-поля — additive; смена семантики Url/TTL — breaking. Kafka-контракта нет.
  • Logs: recording download issued (RecordingId, участник, ExpiresAt) — без Url; metrics: messenger_recording_download_total{result}, messenger_storage_presign_latency_seconds; correlation через traceparent. Url/пресайн/бинарь в логи/трейсы/аудит не пишутся (только факт выдачи).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы