GET /api/messenger/contacts/requests — Заявки в контакты (входящие/исходящие)
Назначение
Раздел «Заявки» портала: список неподтверждённых связей актора. direction=incoming — заявки, где актор
адресат (можно принять/отклонить); direction=outgoing — заявки, где актор инициатор (можно отозвать).
Показываются только связи со статусом Pending; профиль контрагента маскируется (INV-CT-5: полный профиль
раскрывается лишь при Accepted). Вызывает любой пользователь; ответ постраничный, сортировка — по свежести.
| Поле | Значение |
|---|
| Сервис/контекст | messenger |
| API area | public |
| Feature group | contacts |
| Статус | draft |
| Документ | docs/06-services/messenger/api/contacts/list-contact-requests.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User |
| Auth policy / Permissions | messenger.contacts.manage (каталог — ../../../identity/domain/permission.md) |
| Scope (RBAC) | платформа (person-level, вне tenant, ADR-0052) |
| Record-level | только заявки, где actor ∈ {RequesterUserId, AddresseeUserId} |
| Tenant isolation | не применяется — контакты вне tenant |
| Audit | read (PII под маской); access-log без значений телефона |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/messenger/contacts/requests |
| Success status | 200 |
| Idempotency header | не применяется (GET) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
ActorUserId | identity claims | Guid | Да (вычисляется) | != Guid.Empty | record-level |
Direction | query (direction) | ContactRequestDirection (string) | Да | Incoming / Outgoing | — |
Page | query | int | Нет (default 1) | >= 1 | — |
PageSize | query | int | Нет (default 20) | 1..100 | — |
Сортировка фиксирована — CreatedAt desc (свежие сверху); тай-брейк ContactId asc.
Постраничный контейнер (Items, Page, PageSize, TotalCount). Вложенная ContactRequestItemModel —
проекция ../../domain/contact.md + маскированный профиль
../../domain/messenger-user.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
ContactId | Guid | Да | ContactEntity.Id | — | Для accept/decline/remove. |
Direction | ContactRequestDirection (string) | Да | вход | — | Incoming / Outgoing. |
CounterpartUserId | Guid | Да | сторона пары ≠ актор | — | UserId контрагента. |
FirstName | string | Да | MessengerUserEntity.FirstName | полностью | Имя. |
MiddleName | string? | Нет | MessengerUserEntity.MiddleName | полностью | Отчество. |
LastNameMasked | string | Да | MessengerUserEntity.LastName | LastName[0]+'.' (Pending, INV-MU-3) | Фамилия под маской. |
RequestMessage | string? | Нет | ContactEntity.RequestMessage | — | Сопроводительный текст заявки; null — без сообщения. |
CreatedAt | DateTimeOffset | Да | ContactEntity.CreatedAt | — | Момент подачи заявки; ключ сортировки. |
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь; требуется право
messenger.contacts.manage (иначе —
FORBIDDEN). Актор берётся из идентификационных данных.
- Проверка входа.
Direction — допустимое значение enum (обязательно); Page >= 1; PageSize в
1..100 — иначе VALIDATION_ERROR.
- Что читаем. Выбрать заявки актора со статусом
Pending в выбранном направлении: для Incoming — где
актор адресат, для Outgoing — где актор инициатор (индексы ix_contacts_addressee/ix_contacts_requester
по (UserId, Status)). Профили контрагентов подгружаются пакетно (без N+1). Сортировка CreatedAt desc
(тай-брейк ContactId asc).
- Маппинг и маскирование.
CounterpartUserId — противоположная сторона пары. Профиль всегда маскируется
(заявка Pending, значит не Accepted, INV-MU-3): LastNameMasked = LastName[0]+'.'. Телефон в ленту
заявок не попадает (раскрывается только после Accepted).
- Результат. Постраничный список; пустая выдача →
Items = [], TotalCount = 0, 200.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_ERROR | 400 | Direction не задан/неизвестен, Page/PageSize вне диапазона | «Некорректные параметры запроса» | no retry |
FORBIDDEN | 403 | Нет permission messenger.contacts.manage | «Недостаточно прав» | no retry |
INTERNAL_ERROR | 500 | Инфраструктурный сбой | generic | retry (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.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы