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

GET /api/messenger/blacklist — Мой чёрный список

Постраничный список пользователей, которых актор заблокировал (запрет писать и звонить). Read-only проекция агрегата BlacklistEntity: факт блокировки виден только инициатору (Owner), заблокированному не раскрывается. Экран «Чёрный список» ЛК; по этим строкам клиент скрывает кнопки «написать»/«позвонить». Владелец — контекст messenger, feature group blacklist.

Назначение

Отдать авторизованному пользователю его собственные записи блокировки (кого он заблокировал, необязательная приватная причина, момент блокировки) для управления чёрным списком.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Инвестор / Агент / Руководитель / Менеджер застройщика / Менеджер ПИН)
Auth policy / Permissionsmessenger.blacklist.manage (JWT)
Scope (RBAC)платформа (чёрный список — person-level, вне tenant, ADR-0052)
Record-levelOwnerUserId = actor — форсится сервером, чужой список недостижим (индекс ix_blacklist_owner)
Tenant isolationЗапрос не tenanted: единственная граница — record-level OwnerUserId = actor
Auditread (не sensitive; Reason — приватная заметка владельца, в логи не пишется)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/messenger/blacklist
Success status200
Idempotency headerне применяется (read-only, безопасный метод)
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
OwnerUserIdidentity claimsGuidДаRequired (= actor); из query/тела не принимаетсяRecord-level (own-only)
PagingqueryPagingQueryНетTake 1..200 (default 20), Offset >= 0 (default 0)защита от unbounded
SortqueryOrderByMethod<BlacklistSortField>?Нетизвестное значение enum; default — CreatedAt Desc

Enum BlacklistSortField

ЗначениеСортировка
CreatedAtпо моменту блокировки (default), убывание (ix_blacklist_created_at).

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

Постраничный ответ: список элементов + метаданные пагинации (Total, Take, Offset, HasMore). Дом полей — blacklist.md; display имени — messenger-user.md (маскирование фамилии INV-MU-3).

BlacklistItemModel

ПолеТипОбязательностьИсточникMaskingОписание
BlacklistIdGuidДаBlacklistEntity.IdИдентификатор записи (route для unblock-user).
BlockedUserIdGuidДаBlacklistEntity.BlockedUserIdКого заблокировали.
BlockedDisplayNamestringДаMessengerUserEntity (FirstName MiddleName LastName)Фамилия — первой буквой, если нет Accepted-контакта (INV-MU-3)Отображаемое имя заблокированного.
Reasonstring?НетBlacklistEntity.Reason— (виден только владельцу — это его список)Приватная заметка инициатора; null — без причины.
CreatedAtDateTimeOffsetДаBlacklistEntity.CreatedAtМомент блокировки.

Алгоритм

  1. Контекст и доступ. Операцию выполняет авторизованный пользователь (User); OwnerUserId = актор (из query/тела не принимается — выборка строго по своему списку); требуется право messenger.blacklist.manage (иначе MESSENGER_BLACKLIST_FORBIDDEN).
  2. Проверка входа. Paging.Take ∈ 1..200, Offset >= 0; Sort — известное значение enum BlacklistSortField; иначе ошибка валидации.
  3. Выборка. Записи блокировки владельца (OwnerUserId = actor) с сортировкой по Sort (default CreatedAt Desc, индексы ix_blacklist_owner/ix_blacklist_created_at), постранично; Total — по той же выборке. Для страницы — реплики MessengerUserEntity заблокированных (display-имя + признак Accepted-контакта для снятия маски).
  4. Маппинг. BlockedDisplayName собирается из реплики: полное ФИО при наличии Accepted-контакта пары (OwnerBlocked), иначе фамилия маскируется первой буквой (INV-MU-3). Отсутствующая реплика (лаг консюмера) → безопасный fallback («Пользователь»), строка не отбрасывается.
  5. Результат. Постраничный список BlacklistItemModel (Items + Total/Take/Offset) → HTTP 200; пустой Items при пустом списке — тоже 200 (не 404).

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400неверные Take/Offset/Sort«Проверьте параметры списка»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
MESSENGER_BLACKLIST_FORBIDDEN403нет permission messenger.blacklist.manage«Недостаточно прав»no retry

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

  • Новые optional query-параметры и поля BlacklistItemModel — additive, без breaking change; удаление/переименование/смена типа поля или значения enum — breaking (migration + rollout).
  • Logs: blacklist listed (OwnerUserId, Total; Reason/ФИО не пишутся); metrics: messenger_blacklist_list_total{result}, latency p95 (SLO списков < 500 мс); traces — сквозной trace (traceparent); dashboard pin-messenger.

Acceptance Criteria

  • Happy path. Given Owner с messenger.blacklist.manage и записями блокировки; When GET ?take&offset&sort; Then 200, только записи OwnerUserId = actor, сортировка CreatedAt Desc, display-имена из реплики.
  • Маска фамилии. Given у пары нет Accepted-контакта; When GET; Then BlockedDisplayName с фамилией первой буквой (INV-MU-3); при Accepted-контакте — полное ФИО.
  • Лаг реплики. Given реплика заблокированного отсутствует (лаг консюмера); When GET; Then строка не отброшена, безопасный fallback «Пользователь».
  • Пустой список. Given у актора нет блокировок; When GET; Then 200, Items = [] (не 404).
  • Приватность. Given любой актор; When GET; Then виден только собственный список (record-level OwnerUserId = actor, из query/тела не принимается); Reason в логи не пишется.
  • Валидация. Given Take вне 1..200 / неизвестный Sort; When GET; Then 400 VALIDATION_FAILED.
  • Доступ. Given нет messenger.blacklist.manage; When GET; Then 403 MESSENGER_BLACKLIST_FORBIDDEN.

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

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

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