DELETE /api/messenger/contacts/{contactId} — Удалить контакт / отозвать исходящую заявку
Назначение
Разрывает связь одной ручкой в двух сценариях (Pending/Accepted → Cancelled, мягкое удаление; строка
выходит из ограничения активной пары ux_contacts_active_pair):
- удаление подтверждённого контакта (
Accepted) — любой стороной пары: пропадает право звонка
(../../domain/call-session.md, INV-CT-5), профиль снова маскируется;
- отзыв собственной исходящей заявки (
Pending) — только инициатором (INV-CT-3).
Вызывает пользователь; тело ответа не возвращается. Интеграционного события нет (приватно); контрагенту уходит
только realtime-push ContactRemoved. Отклонение входящей заявки адресатом — отдельная ручка
decline-contact-request.md.
| Поле | Значение |
|---|
| Сервис/контекст | messenger |
| API area | public |
| Feature group | contacts |
| Статус | draft |
| Документ | docs/06-services/messenger/api/contacts/remove-contact.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User |
| Auth policy / Permissions | messenger.contacts.manage (каталог — ../../../identity/domain/permission.md) |
| Scope (RBAC) | платформа (person-level, вне tenant, ADR-0052) |
| Record-level | Accepted — actor ∈ {Requester, Addressee}; Pending — только actor == Requester (INV-CT-3) |
| Tenant isolation | не применяется — контакты вне tenant |
| Audit | delete (soft: DeletedAt, DeletedByUserId; переход Cancelled, RespondedByUserId = actor) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | DELETE |
| Route | /api/messenger/contacts/{contactId} |
| Success status | 204 (удалено/отозвано); идемпотентный повтор на Cancelled → 204 |
| Idempotency header | не применяется (идемпотентность — по целевому статусу Cancelled, см. Алгоритм) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims | Guid | Да (вычисляется) | должен ∈ {Requester, Addressee} | record-level (INV-CT-3) |
ContactId | route | Guid | Да | != Guid.Empty; существующая связь | — |
Модель ответа <нет тела>
Успех — 204 No Content (тела нет). Ошибки — ProblemDetails (см. ниже). Обновление списков контактов/заявок
интерфейс получает из realtime-push ContactRemoved либо перезапросом
list-contacts.md / list-contact-requests.md.
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь; требуется право
messenger.contacts.manage
(иначе — FORBIDDEN). Идентификатор связи contactId берётся из маршрута.
- Проверка входа.
ContactId непуст — иначе VALIDATION_FAILED.
- Загрузка связи и проверка членства. Найти связь по
contactId; если её нет — NOT_FOUND. Актор должен
быть одной из сторон пары (actor ∈ {RequesterUserId, AddresseeUserId}), иначе — MSG_CONTACT_FORBIDDEN.
- Бизнес-правила и переход. Идемпотентность: если связь уже
Cancelled/Declined и мягко удалена —
ничего не менять (204). Матрица допустимости (INV-CT-3): Accepted разрывает любая сторона пары;
Pending отзывает только инициатор (адресат для Pending использует decline, иначе —
MSG_CONTACT_FORBIDDEN). Иначе перевести связь в Cancelled (мягкое удаление: DeletedAt = now,
DeletedByUserId = actor, RespondedByUserId = actor); для Accepted — отзывается право звонка
(../../domain/call-session.md, INV-CT-5). Гонка Remove/Accept —
конфликт MSG_CONTACT_CONFLICT.
- Что меняется. Обновляется единственный агрегат связи (мягкое удаление); пара освобождается. Переход и
аудит фиксируются в одном согласованном изменении.
- Факты и события. Интеграционное событие не публикуется (приватно). Контрагенту уходит realtime-push
ContactRemoved (обновить список/скрыть звонок).
- Результат.
204 No Content. Ошибки — 400/403/404/409.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Интеграционные события | нет (удаление контакта приватно) |
| Проекции / поиск | освобождается ограничение активной пары ux_contacts_active_pair — пара снова доступна для заявки; отозвано право звонка |
| Уведомления | realtime-push ContactRemoved контрагенту (без email/push из notifications) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | ContactId пуст/некорректен | «Проверьте запрос» | no retry |
FORBIDDEN | 403 | Нет permission | «Недостаточно прав» | no retry |
MSG_CONTACT_FORBIDDEN | 403 | Актор вне пары; либо Pending и актор — адресат (нужен decline) | «Действие недоступно» | no retry |
NOT_FOUND | 404 | Связь не найдена | «Контакт не найден» | no retry |
MSG_CONTACT_CONFLICT | 409 | Гонка Remove/Accept | «Повторите — состояние изменилось» | retry (перечитать) |
Повтор на уже Cancelled — не ошибка: идемпотентный 204.
Acceptance Criteria
- Удаление подтверждённого контакта. Дано
Accepted-связь и актор — одна из сторон пары. Когда
DELETE. Тогда связь → Cancelled (мягко: DeletedAt/DeletedByUserId/RespondedByUserId = actor),
ограничение активной пары ux_contacts_active_pair освобождается, право звонка отозвано (INV-CT-5),
контрагенту уходит realtime-push ContactRemoved, ответ 204.
- Отзыв своей исходящей заявки. Дано
Pending-связь и актор = Requester. Когда DELETE. Тогда связь
→ Cancelled, 204; адресат перестаёт видеть заявку (INV-CT-3).
- Адресат пытается удалить входящую заявку. Дано
Pending и актор = Addressee. Когда DELETE. Тогда
MSG_CONTACT_FORBIDDEN (403) — для отклонения входящей нужен decline-contact-request, не эта ручка.
- Актор вне пары. Дано связь, где актор не
Requester и не Addressee. Когда DELETE. Тогда
MSG_CONTACT_FORBIDDEN (403), состояние не меняется.
- Идемпотентный повтор. Дано связь уже
Cancelled/Declined и мягко удалена. Когда повторный DELETE.
Тогда 204 без изменений (целевой статус достигнут).
- Гонка
Remove/Accept. Дано Pending-связь, параллельно приходят Accept (адресат) и Remove
(инициатор). Когда обе доходят. Тогда одна применяется, проигравшая получает MSG_CONTACT_CONFLICT (409)
и перечитывает актуальное состояние.
- Связь не найдена. Дано несуществующий
contactId. Когда DELETE. Тогда NOT_FOUND (404).
- Приватность. В лог пишутся
actor/contactId/priorStatus; ФИО и телефон контрагента не логируются;
интеграционного Kafka-события нет.
Совместимость и наблюдаемость
- Контракт стабилен (нет тела ответа); изменение матрицы допустимости переходов — breaking для UI.
- Logs: actor,
contactId, priorStatus (без ФИО/телефона); metrics
messenger_contacts_removed_total{prior_status,result}, latency p95; traces через traceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы