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

GET /api/messenger/contacts/requests — Заявки в контакты (входящие/исходящие)

Назначение

Раздел «Заявки» портала: список неподтверждённых связей актора. direction=incoming — заявки, где актор адресат (можно принять/отклонить); direction=outgoing — заявки, где актор инициатор (можно отозвать). Показываются только связи со статусом Pending; профиль контрагента маскируется (INV-CT-5: полный профиль раскрывается лишь при Accepted). Вызывает любой пользователь; ответ постраничный, сортировка — по свежести.

Metadata

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

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

ПроверкаЗначение
Actor typeUser
Auth policy / Permissionsmessenger.contacts.manage (каталог — ../../../identity/domain/permission.md)
Scope (RBAC)платформа (person-level, вне tenant, ADR-0052)
Record-levelтолько заявки, где actor ∈ {RequesterUserId, AddresseeUserId}
Tenant isolationне применяется — контакты вне tenant
Auditread (PII под маской); access-log без значений телефона

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/messenger/contacts/requests
Success status200
Idempotency headerне применяется (GET)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДа (вычисляется)!= Guid.Emptyrecord-level
Directionquery (direction)ContactRequestDirection (string)ДаIncoming / Outgoing
PagequeryintНет (default 1)>= 1
PageSizequeryintНет (default 20)1..100

Сортировка фиксирована — CreatedAt desc (свежие сверху); тай-брейк ContactId asc.

Модель ответа — постраничный список ContactRequestItemModel

Постраничный контейнер (Items, Page, PageSize, TotalCount). Вложенная ContactRequestItemModel — проекция ../../domain/contact.md + маскированный профиль ../../domain/messenger-user.md.

ПолеТипОбязательностьИсточникMaskingОписание
ContactIdGuidДаContactEntity.IdДля accept/decline/remove.
DirectionContactRequestDirection (string)ДавходIncoming / Outgoing.
CounterpartUserIdGuidДасторона пары ≠ акторUserId контрагента.
FirstNamestringДаMessengerUserEntity.FirstNameполностьюИмя.
MiddleNamestring?НетMessengerUserEntity.MiddleNameполностьюОтчество.
LastNameMaskedstringДаMessengerUserEntity.LastNameLastName[0]+'.' (Pending, INV-MU-3)Фамилия под маской.
RequestMessagestring?НетContactEntity.RequestMessageСопроводительный текст заявки; null — без сообщения.
CreatedAtDateTimeOffsetДаContactEntity.CreatedAtМомент подачи заявки; ключ сортировки.

Алгоритм

  1. Контекст и доступ. Операцию выполняет пользователь; требуется право messenger.contacts.manage (иначе — FORBIDDEN). Актор берётся из идентификационных данных.
  2. Проверка входа. Direction — допустимое значение enum (обязательно); Page >= 1; PageSize в 1..100 — иначе VALIDATION_ERROR.
  3. Что читаем. Выбрать заявки актора со статусом Pending в выбранном направлении: для Incoming — где актор адресат, для Outgoing — где актор инициатор (индексы ix_contacts_addressee/ix_contacts_requester по (UserId, Status)). Профили контрагентов подгружаются пакетно (без N+1). Сортировка CreatedAt desc (тай-брейк ContactId asc).
  4. Маппинг и маскирование. CounterpartUserId — противоположная сторона пары. Профиль всегда маскируется (заявка Pending, значит не Accepted, INV-MU-3): LastNameMasked = LastName[0]+'.'. Телефон в ленту заявок не попадает (раскрывается только после Accepted).
  5. Результат. Постраничный список; пустая выдача → Items = [], TotalCount = 0, 200.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_ERROR400Direction не задан/неизвестен, Page/PageSize вне диапазона«Некорректные параметры запроса»no retry
FORBIDDEN403Нет permission messenger.contacts.manage«Недостаточно прав»no retry
INTERNAL_ERROR500Инфраструктурный сбойgenericretry (backoff)

Acceptance Criteria

  • Входящие (happy). Дано: у актора есть Pending-заявки, где он адресат. Когда запрашивает direction=incoming. Тогда 200 со страницей CreatedAt desc; фамилия контрагента маскирована (LastName[0]+'.', INV-MU-3), телефон не отдаётся.
  • Исходящие. Дано: у актора есть Pending-заявки, где он инициатор. Когда запрашивает direction=outgoing. Тогда 200 со своими исходящими заявками (доступен отзыв), профиль маскирован.
  • Только Pending. Дано: часть связей уже Accepted/Declined/Cancelled. Когда запрашивает. Тогда в выдаче только Pending (закрытые заявки не показываются).
  • Пустая выдача. Дано: заявок нет. Когда запрашивает. Тогда 200 c Items = [], TotalCount = 0.
  • Не задан/неизвестен Direction. Дано: direction отсутствует или вне enum. Когда запрашивает. Тогда 400 VALIDATION_ERROR.
  • Некорректная пагинация. Дано: Page < 1 или PageSize вне 1..100. Когда запрашивает. Тогда 400 VALIDATION_ERROR.
  • Нет прав. Дано: у актора нет messenger.contacts.manage. Тогда 403 FORBIDDEN.

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

  • Новые optional поля — additive; смена набора значений Direction/правил маскирования — breaking для UI.
  • Logs: actor, direction, TotalCount (без ФИО/телефона); metrics messenger_contact_requests_list_total{direction}, latency p95; traces через traceparent.

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

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

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