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

POST /api/messenger/conferences/{conferenceId}/participants/{userId}/remove — Удалить участника из конференции

Модератор конференции исключает активного участника. Его строка ростера (call-participant.md) переводится Joined → Left с LeaveReason = RemovedByModerator, media-стеку отдаётся команда выкинуть окупанта из комнаты (kick), ActiveParticipantCount пересчитывается. Дом полей, статусы и инварианты — conference-session.md.

Назначение

Дать модератору встречи убрать участника из идущей конференции (нарушитель, ошибочно приглашённый, лишний): зафиксировать выход по причине RemovedByModerator и физически выселить его из медиа-комнаты, чтобы он не слышал/не видел встречу. На выходе — актуальная карточка конференции. Свой собственный выход модератор делает через leave-conference.md, а не этим методом. Owning-сервис — messenger, feature group conferences.

Metadata

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

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

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

HTTP-контракт

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

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

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

Тела запроса нет. LeaveReason не передаётся клиентом — фиксируется сервером как RemovedByModerator.

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

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

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

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

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

Предусловия. Актор авторизован и имеет messenger.conferences.use; активный участник Deal-чата (INV-CHAT-4) и модератор конференции (ConferenceRole = Moderator, INV-CONF-6). Конференция Active. Цель {userId} — активный (Joined) участник этой конференции.

Постусловия. Строка ростера цели — State = Left, LeftAt, LeaveReason = RemovedByModerator, DurationSeconds = LeftAt − JoinedAt (≥ 0) (INV-CPART-2/3); ActiveParticipantCount пересчитан. Media-стеку отдана команда выселить окупанта из комнаты (kick); фактический eject подтверждает presence-вебхук (jitsi-events.md, INV-CPART-5). Поднят ConferenceParticipantLeftEvent (RemovedByModerator) → в той же транзакции записано намерение доставки (persist-then-push): после коммита realtime-dispatch пушит MediaParticipant(Left) в chat:{ChatId}. Kafka-события удаление не публикует.

Алгоритм

  1. Контекст и доступ. Операцию выполняет модератор (User); ActorUserId — из JWT; требуется право messenger.conferences.use. Конференции включены флагом CONFERENCE_CALLS_ENABLED, иначе — MSG_CONFERENCES_DISABLED.
  2. Проверка входа. ConferenceId и UserId непустые; иначе ошибка валидации. Тела запроса нет.
  3. Загрузка сессии. Конференция по ConferenceId; нет → MSG_CONFERENCE_NOT_FOUND. Требуется статус Active; иначе MSG_CONFERENCE_NOT_ACTIVE.
  4. Право модератора. Актор — активный участник Deal-чата (INV-CHAT-4), иначе MSG_CHAT_ACCESS_DENIED; его строка ростера — ConferenceRole = Moderator (INV-CONF-6), иначе MSG_CONFERENCE_FORBIDDEN (обычный участник удалять других не может).
  5. Цель и переход. Строка (ConferenceId, UserId) в статусе Joined; нет активной строки цели → MSG_CONFERENCE_PARTICIPANT_NOT_FOUND (цель не в комнате). Если строка уже Left — идемпотентный no-op (200, состояние не меняется, лишнего push/kick нет). При переходе — State = Left, LeftAt, LeaveReason = RemovedByModerator, DurationSeconds = LeftAt − JoinedAt (INV-CPART-2/3); пересчитывается ActiveParticipantCount. Крайний случай самоудаления модератора (обычно — через leave-conference.md): если модератор снял себя и остаются активные — роль модератора передаётся старейшему активному (INV-CONF-6).
  6. Выселение из комнаты. Media-стеку отдаётся команда kick окупанта UserId из комнаты (внешний вызов); фактический eject подтверждает presence-вебхук media-стека (INV-CPART-5, ростер сходится к серверному событию). Недоступность media-стека не откатывает зафиксированный выход — окупант будет выселен по presence-сверке.
  7. Присутствие. В том же согласованном изменении поднимается ConferenceParticipantLeftEvent (RemovedByModerator) и фиксируется намерение push MediaParticipant(Left) в chat:{ChatId} (persist-then-push); при передаче модерации — ещё ConferenceSignal. Всё — одна транзакция (атомарно).
  8. Результат. Актуальная карточка ConferenceModel (без Room) → HTTP 200 OK.

Диаграмма

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

ТипДетали
Integration eventsnone — удаление участника Kafka не публикует
Realtime push (hub)MediaParticipant(Left)chat:{ChatId}; при передаче модерации — ConferenceSignal (messenger-hub.md)
External callsкоманда kick окупанта в media-стек (SFU, ADR-0062); подтверждение — presence-вебхук (jitsi-events.md)
Cache / projections / searchпересчёт ActiveParticipantCount; ростер (ix_call_participants_active)
Notificationsнет

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ConferenceId/UserId = Empty«Проверьте параметры запроса»no retry
MSG_CONFERENCE_NOT_FOUND404конференции нет«Конференция не найдена»no retry
MSG_CONFERENCE_NOT_ACTIVE409конференция не Active«Конференция не активна»no retry (перечитать)
MSG_CHAT_ACCESS_DENIED403актор не активный участник Deal-чата«Нет доступа к конференции»no retry
MSG_CONFERENCE_FORBIDDEN403актор не модератор (ConferenceRole != Moderator)«Недостаточно прав: нужен модератор»no retry
MSG_CONFERENCE_PARTICIPANT_NOT_FOUND404у цели нет активной (Joined) строки ростера«Участник не найден в конференции»no retry
MSG_CONFERENCES_DISABLED503CONFERENCE_CALLS_ENABLED = false«Конференции временно недоступны»retry позже

Acceptance Criteria

  • Удаление участника (happy). Дано: актор — модератор Active-конференции, цель Joined. Когда POST /participants/{userId}/remove. Тогда 200, строка цели → Left (RemovedByModerator, DurationSeconds), ActiveParticipantCount уменьшен; media-стеку отдан kick; остальные получают MediaParticipant(Left).
  • Не модератор. Дано: актор — обычный участник (Speaker). Когда пытается удалить другого. Тогда 403 MSG_CONFERENCE_FORBIDDEN, состояние не меняется.
  • Цель не в комнате. Дано: у цели нет активной (Joined) строки (уже вышла / не входила). Когда POST /remove. Тогда 404 MSG_CONFERENCE_PARTICIPANT_NOT_FOUND.
  • Идемпотентность. Дано: цель уже Left (повтор удаления). Когда POST /remove. Тогда 200 no-op без ошибки, лишнего push/kick нет.
  • Конференция не активна. Дано: конференция Ended/Provisioning/Failed. Когда POST /remove. Тогда 409 MSG_CONFERENCE_NOT_ACTIVE.
  • Media-стек недоступен. Дано: SFU не принял команду kick. Когда POST /remove. Тогда зафиксированный выход не откатывается (200), окупант выселяется по presence-сверке (INV-CPART-5); присутствие сходится к серверному ростеру.
  • Не участник чата. Дано: актор не активный участник Deal-чата. Когда POST /remove. Тогда 403 MSG_CHAT_ACCESS_DENIED.
  • Нет конференции. Дано: ConferenceId не существует. Тогда 404 MSG_CONFERENCE_NOT_FOUND.

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

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

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

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

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