POST /api/messenger/conferences/{conferenceId}/join — Присоединиться к конференции
Активный участник Deal-чата входит в идущую конференцию. Заводится/реактивируется строка ростера
(call-participant.md) в состоянии Joined, актору выдаётся свежий дескриптор
входа в медиа-комнату. Дом полей и инварианты (INV-CONF-*, INV-CPART-*) —
conference-session.md.
Назначение
Позволить участнику чата сделки войти в уже поднятую конференцию (или переподключиться после разрыва). Операция
идемпотентна по паре (конференция, пользователь): повторный вход реактивирует ту же строку ростера и выдаёт
новый токен доступа, а не создаёт дубль. На выходе — карточка конференции плюс дескриптор входа Room.
Owning-сервис — messenger, feature group conferences.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | messenger |
| API area | conferences |
| Feature group | conferences |
| Статус | draft |
| Документ | docs/06-services/messenger/api/conferences/join-conference.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User — активный участник Deal-чата конференции |
| Auth policy / Permissions | permission messenger.conferences.use |
| Scope (RBAC) | не применяется — доступ по членству в чате |
| Record-level | активный ParticipantEntity чата ChatId конференции (INV-CHAT-4) |
| Tenant isolation | не применяется (кросс-tenant); срез — по членству в Deal-чате |
| Audit | update ростера: строка участника (Joined, JoinedAt); реактивация при реконнекте |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/messenger/conferences/{conferenceId}/join |
| Success status | 200 (OK) |
| Idempotency header | не применяется — повтор реактивирует ту же строку ростера (INV-CPART-1), выдаётся свежий токен |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
ConferenceId | route | Guid | Да | != Empty; существующая конференция в Active (шаги 3–4) | — |
ActorUserId | identity claims | Guid | Да | из JWT; активный участник ChatId конференции | — |
Тела запроса нет.
Модель ответа ConferenceJoinModel
Обёртка «карточка + вход»: карточка конференции (дом полей — start-conference.md,
ConferenceModel) и обязательный дескриптор входа в медиа-комнату (MediaJoinModel, дом —
messenger-hub.md).
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Conference | ConferenceModel | Да | проекция ConferenceSessionEntity | — | Карточка с ростером; Conference.Room продублирован в Room. |
Room | MediaJoinModel | Да | вычисляется | Sensitive: AccessToken/TURN-креды не логируются | Свежий дескриптор входа (RoomName, AccessToken, SignalingUrl, MediaDomain, IceServers, ExpiresAt). |
Предусловия и постусловия
Предусловия. Актор авторизован и имеет messenger.conferences.use; флаг CONFERENCE_CALLS_ENABLED включён.
Актор — активный участник Deal-чата конференции (INV-CHAT-4). Конференция существует и в статусе Active
(INV-CONF-4).
Постусловия. Строка ростера (ConferenceId, ActorUserId) существует в State = Joined с новым JoinedAt
(создана при первом входе либо реактивирована при реконнекте, INV-CPART-1 — дубля нет); LeftAt/LeaveReason
сброшены. Пересчитаны ActiveParticipantCount/PeakParticipantCount. Актору выдан эфемерный Room
(AccessToken не персистится, INV-CONF-7). В канал chat:{ChatId} отправлен MediaParticipant(Joined).
Kafka-события не публикуются. Роль модерации входом не назначается (остаётся Speaker, если ранее не был
модератором).
Алгоритм
- Контекст и доступ. Операцию выполняет участник Deal-чата (
User);ActorUserId— из JWT; требуется правоmessenger.conferences.use. Конференции включены флагомCONFERENCE_CALLS_ENABLED, иначе —MSG_CONFERENCES_DISABLED. - Проверка входа.
ConferenceIdнепустой; иначе ошибка валидации. - Загрузка сессии и доступ. Конференция по
ConferenceId; нет →MSG_CONFERENCE_NOT_FOUND. Актор — активный участник чатаChatIdконференции (INV-CHAT-4), иначеMSG_CHAT_ACCESS_DENIED. - Состояние. Конференция должна быть
Active(INV-CONF-4);Provisioning/Ended/Failed→MSG_CONFERENCE_NOT_ACTIVE(вProvisioningкомната ещё не подтверждена, в терминале — вход невозможен). - Вход в ростер. Строка
(ConferenceId, ActorUserId)создаётся или реактивируется (INV-CPART-1):State = Joined, новыйJoinedAt,LeftAt/LeaveReasonсбрасываются; орг-рольRole— снимок роли актора в чате. ПересчитываетсяActiveParticipantCount/PeakParticipantCount. Роль модерации не назначается (остаётсяSpeaker, если ранее не был модератором). - Приглашение/присутствие. В том же изменении фиксируется намерение push
MediaParticipant(Joined)вchat:{ChatId}(persist-then-push). Дескриптор входаRoomвыдаётся в ответе;AccessTokenкороткоживущий, в БД не хранится (INV-CONF-7). - Результат.
ConferenceJoinModel(карточка +Room) → HTTP200 OK.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none |
| Realtime push (hub) | MediaParticipant(Joined) → chat:{ChatId} |
| Токены доступа | Room.AccessToken/TURN-креды короткоживущие, в БД/realtime_outbox не хранятся, не логируются |
| Cache / projections / search | пересчёт ActiveParticipantCount/PeakParticipantCount; ростер — partial ix_call_participants_active |
| Notifications | нет |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | ConferenceId = Empty | «Проверьте параметры запроса» | no retry |
MSG_CONFERENCE_NOT_FOUND | 404 | конференции нет | «Конференция не найдена» | no retry |
MSG_CHAT_ACCESS_DENIED | 403 | актор не активный участник Deal-чата | «Нет доступа к конференции» | no retry |
MSG_CONFERENCE_NOT_ACTIVE | 409 | статус не Active (Provisioning/Ended/Failed) | «Конференция ещё не готова или уже завершена» | no retry (перечитать) |
MSG_CONFERENCES_DISABLED | 503 | CONFERENCE_CALLS_ENABLED = false | «Конференции временно недоступны» | retry позже |
Acceptance Criteria
- Первый вход (happy). Дано: актор — активный участник Deal-чата, конференция
Active, актор ещё не в ростере. Когда присоединяется. Тогда200сConferenceJoinModel, заведена строка ростераJoined,ActiveParticipantCountувеличен, выданRoom; участники получаютMediaParticipant(Joined). - Реконнект (идемпотентность). Дано: у актора есть строка ростера в
Leftпосле разрыва. Когда присоединяется снова. Тогда та же строка реактивируется (Left → Joined, новыйJoinedAt, INV-CPART-1), дубля не создаётся, выдан свежийRoom. - Повторный join активного участника. Дано: актор уже
Joined. Когда присоединяется повторно. Тогда200со свежим токеном,ActiveParticipantCountне задваивается. - Конференция ещё не готова / завершена. Дано: статус
Provisioning,EndedилиFailed. Когда актор присоединяется. Тогда409 MSG_CONFERENCE_NOT_ACTIVE(вProvisioningкомната не подтверждена; в терминале вход невозможен). - Не участник чата. Дано: актор не активный участник Deal-чата. Когда присоединяется. Тогда
403 MSG_CHAT_ACCESS_DENIED. - Нет конференции. Дано:
ConferenceIdне существует. Тогда404 MSG_CONFERENCE_NOT_FOUND. - Флаг выключен. Дано:
CONFERENCE_CALLS_ENABLED = false. Тогда503 MSG_CONFERENCES_DISABLED.
Совместимость и наблюдаемость
- Новые optional поля ответа — additive; смена маршрута/семантики статуса — breaking. Kafka-контракта нет.
- Logs:
conference join requested/joined/not-active(ConferenceId,ActorUserId; токены — никогда в логи); metrics:messenger_conference_join_total{result}, latency p95; traces — сквозной trace (traceparent); dashboardpin-messenger.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Entity / Aggregate: ConferenceSessionEntity — групповой звонок в чате сделки, Entity: CallParticipantEntity (owned-child ConferenceSessionEntity)
- API (9): GET /api/messenger/conferences/{conferenceId} — Карточка конференции, POST /api/messenger/chats/{chatId}/conferences — Начать конференцию в чате сделки, POST /api/messenger/conferences/{conferenceId}/decline — Отклонить приглашение в конференцию, POST /api/messenger/conferences/{conferenceId}/end — Завершить конференцию для всех, POST /api/messenger/conferences/{conferenceId}/invite — Пригласить участников в конференцию, POST /api/messenger/conferences/{conferenceId}/leave — Выйти из конференции, POST /api/messenger/conferences/{conferenceId}/media-token — Обновить токен входа в конференцию, POST /api/messenger/conferences/{conferenceId}/participants/{userId}/remove — Удалить участника из конференции, SignalR Hub /hubs/messenger — MessengerHub (realtime-контракт)
- Use Cases (1): UC-MSG-006. Групповой звонок-конференция в чате сделки
- calls (1): Жизненный цикл сессии звонка — 1:1 и конференция (messenger)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность (дом полей,
INV-CONF-4): conference-session.md; ростер (INV-CPART-1): call-participant.md. - Дескриптор входа
MediaJoinModel: messenger-hub.md. - Соседние операции: start-conference.md, leave-conference.md, end-conference.md, conference-media-token.md; контекст: README.
- ADR: 0062 медиа-стек и запись, 0055 свой мини-мессенджер.