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

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.

Metadata

ПолеЗначение
Сервис/контекстmessenger
API areapublic
Feature groupcontacts
Статусdraft
Документdocs/06-services/messenger/api/contacts/remove-contact.md

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

ПроверкаЗначение
Actor typeUser
Auth policy / Permissionsmessenger.contacts.manage (каталог — ../../../identity/domain/permission.md)
Scope (RBAC)платформа (person-level, вне tenant, ADR-0052)
Record-levelAcceptedactor ∈ {Requester, Addressee}; Pending — только actor == Requester (INV-CT-3)
Tenant isolationне применяется — контакты вне tenant
Auditdelete (soft: DeletedAt, DeletedByUserId; переход Cancelled, RespondedByUserId = actor)

HTTP-контракт

ПолеЗначение
MethodDELETE
Route/api/messenger/contacts/{contactId}
Success status204 (удалено/отозвано); идемпотентный повтор на Cancelled204
Idempotency headerне применяется (идемпотентность — по целевому статусу Cancelled, см. Алгоритм)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДа (вычисляется)должен ∈ {Requester, Addressee}record-level (INV-CT-3)
ContactIdrouteGuidДа!= Guid.Empty; существующая связь

Модель ответа <нет тела>

Успех — 204 No Content (тела нет). Ошибки — ProblemDetails (см. ниже). Обновление списков контактов/заявок интерфейс получает из realtime-push ContactRemoved либо перезапросом list-contacts.md / list-contact-requests.md.

Алгоритм

  1. Контекст и доступ. Операцию выполняет пользователь; требуется право messenger.contacts.manage (иначе — FORBIDDEN). Идентификатор связи contactId берётся из маршрута.
  2. Проверка входа. ContactId непуст — иначе VALIDATION_FAILED.
  3. Загрузка связи и проверка членства. Найти связь по contactId; если её нет — NOT_FOUND. Актор должен быть одной из сторон пары (actor ∈ {RequesterUserId, AddresseeUserId}), иначе — MSG_CONTACT_FORBIDDEN.
  4. Бизнес-правила и переход. Идемпотентность: если связь уже 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.
  5. Что меняется. Обновляется единственный агрегат связи (мягкое удаление); пара освобождается. Переход и аудит фиксируются в одном согласованном изменении.
  6. Факты и события. Интеграционное событие не публикуется (приватно). Контрагенту уходит realtime-push ContactRemoved (обновить список/скрыть звонок).
  7. Результат. 204 No Content. Ошибки — 400/403/404/409.

Диаграмма

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

ТипДетали
Интеграционные событиянет (удаление контакта приватно)
Проекции / поискосвобождается ограничение активной пары ux_contacts_active_pair — пара снова доступна для заявки; отозвано право звонка
Уведомленияrealtime-push ContactRemoved контрагенту (без email/push из notifications)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400ContactId пуст/некорректен«Проверьте запрос»no retry
FORBIDDEN403Нет permission«Недостаточно прав»no retry
MSG_CONTACT_FORBIDDEN403Актор вне пары; либо Pending и актор — адресат (нужен decline)«Действие недоступно»no retry
NOT_FOUND404Связь не найдена«Контакт не найден»no retry
MSG_CONTACT_CONFLICT409Гонка 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.

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

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

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