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

GET /api/messenger/calls/{callId}/recordings — Записи 1:1-звонка

Список записей конкретного 1:1-звонка (call-session.md). Каждая запись — проекция CallRecordingEntity (дом полей, статусы, инварианты — там). Запись стартует по политике (Trigger = AutoPolicy); ручного старта нет.

Назначение

Вернуть участнику звонка (Caller/Callee) записи этого звонка — метаданные без бинаря и без ссылки на скачивание (ссылка выдаётся отдельным методом get-recording-download.md). Обычно на звонок приходится одна запись, но контракт возвращает коллекцию (устойчив к сегментации/повтору). Owning- сервис — messenger, feature group recordings.

Metadata

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

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

ПроверкаЗначение
Actor typeUser — участник звонка (Caller или Callee)
Auth policy / Permissionspermission messenger.recordings.view
Scope (RBAC)платформа (person-level)
Record-levelдоступ по membership звонка: actor.UserId ∈ {CallerUserId, CalleeUserId}
Tenant isolationне применяется (ADR-0052); изоляция — по паре Caller/Callee
Auditread (нечувствительный): выдаются метаданные без FileId-ссылки/URL

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
CallIdrouteGuidДа!= Empty; существующий звонок, актор — участник (шаги 3–4)
ActorUserIdidentity claimsGuidДаиз JWT; ∈ {Caller, Callee}
TakequeryintНет1..100, default 20
OffsetqueryintНет>= 0, default 0

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

Коллекция никогда не возвращается «сырой» — оборачивается в RecordingListModel.

ПолеТипОбязательностьИсточникMaskingОписание
ItemsRecordingEntry[]Дапроекция записей звонкаЗаписи звонка (обычно 0–1).
TotalintДаcomputedВсего записей по звонку (для пагинации).
TakeintДавход/echoРазмер страницы.
OffsetintДавход/echoСмещение страницы.

Элемент RecordingEntry

Компактная проекция CallRecordingEntity для списков (без ссылки на файл).

ПолеТипОбязательностьИсточникMaskingОписание
RecordingIdGuidДаCallRecordingEntity.IdИдентификатор записи.
SessionKindSessionKindДа.SessionKindDirect для 1:1-звонка.
SessionIdGuidДа.CallSessionIdСессия-владелец (= CallId).
StatusRecordingStatusДа.StatusPending/Recording/Uploading/Available/Failed/Deleted.
TriggerRecordingTriggerДа.TriggerAutoPolicy для авто-записи.
MediaTypeCallMediaTypeДа.MediaTypeAudio/Video.
DurationSecondsint?Нет.DurationSecondsДлительность; null до Available.
SizeByteslong?Нет.SizeBytesРазмер файла; null до Available.
FormatRecordingFormat?Нет.FormatКонтейнер (Mp4/Webm/Mkv/Mp3); null до Available.
CreatedAtDateTimeOffsetДа.CreatedAtМомент провизии записи.
AvailableAtDateTimeOffset?Нет.AvailableAtМомент готовности (→ Available).

Ссылка FileId/URL в список не входит — скачивание идёт через get-recording-download.md (аудит ReadSensitive).

Алгоритм

  1. Контекст и доступ. Операцию выполняет участник звонка (User, Caller или Callee); ActorUserId — из JWT; требуется право messenger.recordings.view. Запись включена флагом RECORDINGS_ENABLED, иначе MSG_RECORDINGS_DISABLED.
  2. Проверка входа. CallId непустой; Take/Offset в допустимых диапазонах — иначе ошибка валидации.
  3. Загрузка звонка. Звонок по CallId; нет → MSG_CALL_NOT_FOUND.
  4. Право. Актор должен быть участником: ActorUserId ∉ {Caller, Callee}MSG_CALL_FORBIDDEN (либо MSG_CALL_NOT_FOUND для сокрытия чужого звонка).
  5. Что читаем. Записи с SessionKind = Direct и CallSessionId == CallId, сортировка по CreatedAt убыв., страница Take/Offset; Total — общее число. Порядок стабилен (по CreatedAt убыв.); на звонок обычно 0–1 записи, поэтому конкурентное появление новой записи в ходе пагинации на практике не создаёт сдвига страниц. Статусы записей — снимок на момент чтения (запись, дозревающая до Available, при повторном запросе покажет обновлённые DurationSeconds/SizeBytes/Format/AvailableAt).
  6. Результат. RecordingListModel (метаданные без ссылок) → HTTP 200 OK. Пустой список — валидный ответ (записи ещё нет / политика не включала авто-запись).

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400CallId = Empty / Take/Offset вне диапазона«Проверьте параметры запроса»no retry
MSG_CALL_NOT_FOUND404звонка нет или актор не участник (сокрытие)«Звонок не найден»no retry
MSG_CALL_FORBIDDEN403актор не Caller/Callee«Недостаточно прав»no retry
MSG_RECORDINGS_DISABLED503RECORDINGS_ENABLED = false«Записи временно недоступны»retry позже

Acceptance Criteria

  • Given звонок с одной Available-записью, актор — участник, When запрашивает список, Then 200, Items содержит 1 элемент с метаданными без FileId-ссылки, Total = 1.
  • Given звонок без записей (политика не включала авто-запись), When участник запрашивает список, Then 200, Items = [], Total = 0 (пустой — валидный ответ, не ошибка).
  • Given актор не Caller/Callee, When запрашивает список, Then 404 MSG_CALL_NOT_FOUND (сокрытие чужого звонка).
  • Given Take/Offset вне диапазона (Take ∉ 1..100 / Offset < 0), When запрос, Then 400 VALIDATION_FAILED.
  • Given RECORDINGS_ENABLED = false, When любой запрос, Then 503 MSG_RECORDINGS_DISABLED.

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

  • Новые optional-поля RecordingEntry — additive; удаление/смена типа полей — breaking. Kafka-контракта нет.
  • Logs: call recordings listed (CallId, Total, участник); metrics: messenger_recordings_list_total{scope="call"}, latency p95; correlation через traceparent; ссылки/URL/бинарь в логи не пишутся.

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

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

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