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

GET /api/messenger/contacts/search — Глобальный поиск пользователя по телефону

Назначение

Находит пользователя платформы по номеру телефона, чтобы отправить ему заявку в контакты или открыть чат. Поиск сквозной (person-level, вне tenant): ищущий вводит телефон, система нормализует его к формату E.164 и ищет точное совпадение среди пользователей мессенджера. До подтверждённого контакта профиль маскирован — «Имя Отчество + первая буква Фамилии» (INV-MU-3); полный профиль раскрывается только при существующем Accepted-контакте (INV-CT-5). Вызывает любой аутентифицированный пользователь; возвращается 0 или 1 результат (телефон уникален).

Metadata

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

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

ПроверкаЗначение
Actor typeUser (любой аутентифицированный пользователь платформы)
Auth policy / Permissionsmessenger.contacts.search (каталог — ../../../identity/domain/permission.md)
Scope (RBAC)платформа (поиск сквозной, person-level, ADR-0052)
Record-levelне применяется (глобальный справочник); результат маскируется до Accepted
Tenant isolationне применяется — пользователи вне tenant
Auditread-sensitive (поиск по PII-телефону); в лог — только факт поиска, телефон маской +7***NNNN

HTTP-контракт

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

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ActorUserIdidentity claimsGuidДа (вычисляется)!= Guid.Emptyисключается из результата (не найти себя)
Phonequery (phone)stringДанормализация к E.164 (^\+[1-9]\d{9,14}$), max 32 после нормализацииPII: маска +7***NNNN в логах

Модель ответа UserSearchResultModel

Эндпоинт возвращает список из 0 или 1 элемента (телефон уникален). Проекция ../../domain/messenger-user.md; маскирование — по RelationshipStatus.

ПолеТипОбязательностьИсточникMaskingОписание
UserIdGuidДаMessengerUserEntity.UserIdАдресат для send-contact-request / открытия чата.
FirstNamestringДаMessengerUserEntity.FirstNameполностьюИмя (отдаётся всегда, INV-MU-3).
MiddleNamestring?НетMessengerUserEntity.MiddleNameполностьюОтчество, если есть.
LastNameMaskedstringДаMessengerUserEntity.LastNameLastName[0]+'.' до Accepted; полностью при AcceptedФамилия под маской (INV-MU-3/INV-CT-5).
PhoneMaskedstringДаMessengerUserEntity.PhoneNumber+7***NNNN до Accepted; полностью при AcceptedТелефон (PII); ищущий уже владеет вводом.
IsActiveboolДаMessengerUserEntity.IsActivefalse — недоступен для новых заявок (INV-MU).
RelationshipStatusContactRelationshipStatus (string)Давычислено из ContactEntityNone/RequestSent/RequestReceived/Contact.
CanSendRequestboolДаcomputedfalse, если уже есть активная связь (Pending/Accepted) или сам пользователь.

Алгоритм

  1. Контекст и доступ. Операцию выполняет аутентифицированный пользователь; требуется право messenger.contacts.search (иначе — FORBIDDEN, fail-closed).
  2. Проверка входа. Phone непуст и нормализуется к E.164; несоответствие формату — VALIDATION_ERROR.
  3. Что читаем. Точечный поиск пользователя по нормализованному телефону (уникальный ux_users_phone); если нет совпадения — пустой список (шаг 5). Самого актора исключаем (нельзя найти себя). Для найденного — статус связи актора с ним по паре (индекс активной пары ux_contacts_active_pair).
  4. Маппинг и маскирование. RelationshipStatus: нет связи → None; Pending и актор инициатор → RequestSent; Pending и актор адресат → RequestReceived; AcceptedContact. При RelationshipStatus == Contact — полные LastName/PhoneNumber; иначе LastNameMasked = LastName[0]+'.', PhoneMasked = +7***NNNN (INV-MU-3). CanSendRequest = RelationshipStatus == None && IsActive (наличие блокировки не раскрывается — приватность ../../domain/blacklist.md; фактический запрет проявится на send-contact-request общей ошибкой).
  5. Результат. Список из 0 или 1 элемента; не найдено → [] (валидное «ничего не найдено», без 404 — не раскрываем существование номера).

Диаграмма

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

ТипДетали
Проекции / поискчтение реплики пользователей (ux_users_phone); внешнего поиска нет (ADR-0056)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_ERROR400Phone пустой / не приводится к E.164«Укажите корректный номер телефона»no retry
FORBIDDEN403Нет permission messenger.contacts.search«Недостаточно прав»no retry
INTERNAL_ERROR500Инфраструктурный сбойgenericretry (backoff)

Отсутствие пользователя — не ошибка: 200 + [] (не раскрываем, зарегистрирован ли номер).

Acceptance Criteria

  • Найден незнакомец. Дано номер зарегистрированного активного пользователя без связи с актором. Когда поиск. Тогда 200 c одним результатом, RelationshipStatus = None, LastNameMasked = LastName[0]+'.', PhoneMasked = +7***NNNN (INV-MU-3), CanSendRequest = true.
  • Найден контакт. Дано с найденным есть Accepted-связь. Когда поиск. Тогда RelationshipStatus = Contact, полные LastName/PhoneNumber (INV-CT-5), CanSendRequest = false.
  • Есть заявка. Дано Pending-связь. Когда поиск: актор-инициатор → RelationshipStatus = RequestSent; актор-адресат → RequestReceived; в обоих случаях CanSendRequest = false.
  • Номер не зарегистрирован. Дано телефон, которого нет среди пользователей. Когда поиск. Тогда 200 + [] (не 404 — существование номера не раскрывается).
  • Поиск себя. Дано актор вводит собственный телефон. Когда поиск. Тогда [] (сам исключён из выдачи).
  • Некорректный номер. Дано телефон не приводится к E.164. Когда поиск. Тогда VALIDATION_ERROR (400).
  • Блокировка не раскрывается. Дано одна сторона в чёрном списке другой. Когда поиск. Тогда результат обычный (CanSendRequest = None && IsActive), факт блокировки скрыт; запрет проявится общей ошибкой лишь на send-contact-request.
  • Приватность в логах. В лог идёт phoneHash/found/relationshipStatus; полный телефон и ФИО не логируются (маска +7***NNNN).

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

  • Новые optional поля результата — additive; смена правил маскирования — breaking для UI (fallback обязателен).
  • ContactRelationshipStatus — enum строкой; клиентский fallback на unknown обязателен.
  • Logs: actor, phoneHash, found (bool), relationshipStatus (без полного телефона/ФИО); metrics messenger_contacts_search_total{found}, latency p95; traces через traceparent.

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

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

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