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

POST /api/messenger/conferences/{conferenceId}/leave — Выйти из конференции

Участник покидает идущую конференцию. Строка ростера (call-participant.md) переводится в Left (soft — строка сохраняется в истории участия), пересчитывается активный состав. Дом полей и инварианты — conference-session.md.

Назначение

Дать участнику корректно выйти из конференции: зафиксировать выход и длительность участия, обновить активный ростер и уведомить остальных. Выход последнего активного участника не завершает сессию мгновенно — конференция остаётся Active короткое grace-окно, после которого её закрывает conference-room-reaper (EmptyTimeout). На выходе — актуальная карточка конференции. Owning-сервис — messenger, feature group conferences.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areaconferences
Feature groupconferences
Статусdraft
Документdocs/06-services/messenger/api/conferences/leave-conference.md

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

ПроверкаЗначение
Actor typeUser — участник конференции (State = Joined)
Auth policy / Permissionspermission messenger.conferences.use
Scope (RBAC)не применяется — доступ по членству в чате
Record-levelактивная строка ростера актора в конференции; членство в Deal-чате (INV-CHAT-4)
Tenant isolationне применяется (кросс-tenant); срез — по членству
Auditupdate ростера: → Left, метки LeftAt/LeaveReason (SelfLeft), DurationSeconds

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/messenger/conferences/{conferenceId}/leave
Success status200 (OK)
Idempotency headerне применяется — повтор на уже Left-строке no-op (участник не активен)
Correlationсквозной trace через traceparent

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

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

Тела запроса нет. LeaveReason при вызове клиентом — SelfLeft; выходы по дисконнекту/удалению модератором фиксируются presence-вебхуком media-стека и end-операцией, не этим методом.

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

Проекция ConferenceSessionEntity с обновлённым ростером (дом полей — start-conference.md). Поле Room отсутствует (выход не выдаёт токен): актор уже покинул комнату.

ПолеТипОбязательностьИсточникMaskingОписание
ConferenceIdGuidДаConferenceSessionEntity.IdИдентификатор конференции.
StatusConferenceStatusДа.StatusОбычно ещё Active (закрытие — за reaper-ом).
ActiveParticipantCountintДа.ActiveParticipantCountАктивных после выхода.
ParticipantsConferenceParticipantEntry[]Даростер (call-participant.md)Актор — с IsActive = false, LeftAt, DurationSeconds.

Полный состав полей карточки — ConferenceModel в start-conference.md; здесь перечислена значимая для выхода дельта, Room = null.

Предусловия и постусловия

Предусловия. Актор авторизован и имеет messenger.conferences.use; активный участник Deal-чата (INV-CHAT-4). Как правило, у актора есть строка ростера в Joined (но отсутствие/Left — допустимый no-op, см. Алгоритм).

Постусловия. Строка ростера актора — State = Left, LeftAt, LeaveReason = SelfLeft, DurationSeconds = LeftAt − JoinedAt (≥ 0) (INV-CPART-2/3); ActiveParticipantCount пересчитан. Если вышел модератор и остались активные — роль модератора передана старейшему активному (INV-CONF-6). В канал chat:{ChatId} отправлен MediaParticipant(Left) (при передаче модерации — ещё ConferenceSignal). Сессия остаётся Active даже при опустошении: фактическое закрытие пустой комнаты с EmptyTimeout выполняет conference-room-reaper. Kafka-события выход не публикует.

Алгоритм

  1. Контекст и доступ. Операцию выполняет участник конференции (User); ActorUserId — из JWT; требуется право messenger.conferences.use.
  2. Проверка входа. ConferenceId непустой; иначе ошибка валидации.
  3. Загрузка сессии. Конференция по ConferenceId; нет → MSG_CONFERENCE_NOT_FOUND. Актор — активный участник Deal-чата (INV-CHAT-4); иначе MSG_CHAT_ACCESS_DENIED.
  4. Выход из ростера. Строка (ConferenceId, ActorUserId): если JoinedState = Left, LeftAt, LeaveReason = SelfLeft, DurationSeconds = LeftAt − JoinedAt (INV-CPART-2/3); если строки нет или она уже Left — no-op (успех, без ошибки). Пересчитывается ActiveParticipantCount. Если вышел модератор и остаются активные — роль передаётся старейшему активному (INV-CONF-6).
  5. Присутствие. В том же изменении фиксируется намерение push MediaParticipant(Left) в chat:{ChatId} (persist-then-push), а при передаче модерации — ConferenceSignal (смена модератора). Если активных не осталось — сессия остаётся Active; фактическое закрытие с EmptyTimeout выполняет conference-room-reaper.
  6. Результат. Актуальная карточка ConferenceModel (без Room) → HTTP 200 OK.

Диаграмма

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

ТипДетали
Integration eventsnone — сам выход Kafka не публикует; закрытие пустой комнаты reaper-ом даёт pin.messenger.conference-ended.v1 (conference-ended.md)
Realtime push (hub)MediaParticipant(Left)chat:{ChatId}; при передаче модерации — ConferenceSignal
Cache / projections / searchпересчёт ActiveParticipantCount; ростер (ix_call_participants_active)
Notificationsнет

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ConferenceId = Empty«Проверьте параметры запроса»no retry
MSG_CONFERENCE_NOT_FOUND404конференции нет«Конференция не найдена»no retry
MSG_CHAT_ACCESS_DENIED403актор не участник Deal-чата«Нет доступа к конференции»no retry

Повтор выхода и выход при уже терминальной конференции — no-op (200, участник не активен), отдельного кода состояния не вводится.

Acceptance Criteria

  • Штатный выход (happy). Дано: актор Joined в Active-конференции, кроме него есть активные. Когда выходит. Тогда 200, его строка → Left с DurationSeconds, ActiveParticipantCount уменьшен; остальные получают MediaParticipant(Left); сессия остаётся Active.
  • Выход модератора. Дано: актор — модератор, есть другие активные. Когда выходит. Тогда роль модератора передана старейшему активному (INV-CONF-6), отправлен ConferenceSignal о смене модератора.
  • Выход последнего. Дано: актор — единственный активный. Когда выходит. Тогда ActiveParticipantCount = 0, но Status остаётся Active; закрытие с EmptyTimeout позже выполнит reaper (не мгновенно).
  • Повторный выход / нет строки (идемпотентность). Дано: строка актора уже Left либо отсутствует. Когда выходит. Тогда 200 no-op без ошибки, состояние не меняется, лишнего push нет.
  • Не участник чата. Дано: актор не активный участник Deal-чата. Когда выходит. Тогда 403 MSG_CHAT_ACCESS_DENIED.
  • Нет конференции. Дано: ConferenceId не существует. Тогда 404 MSG_CONFERENCE_NOT_FOUND.

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

  • Новые optional поля ответа — additive; смена маршрута — breaking. Kafka-контракта у выхода нет.
  • Logs: conference leave requested/left/moderator-transferred (ConferenceId, ActorUserId, DurationSeconds); metrics: messenger_conference_leave_total{result}, latency p95; traces — сквозной trace (traceparent); dashboard pin-messenger.

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

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

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