GET /api/messenger/blacklist — Мой чёрный список
Постраничный список пользователей, которых актор заблокировал (запрет писать и звонить). Read-only
проекция агрегата BlacklistEntity: факт блокировки виден только инициатору
(Owner), заблокированному не раскрывается. Экран «Чёрный список» ЛК; по этим строкам клиент скрывает кнопки
«написать»/«позвонить». Владелец — контекст messenger, feature group blacklist.
Назначение
Отдать авторизованному пользователю его собственные записи блокировки (кого он заблокировал, необязательная
приватная причина, момент блокировки) для управления чёрным списком.
| Поле | Значение |
|---|
| Сервис/контекст | messenger |
| API area | public |
| Feature group | blacklist |
| Статус | draft |
| Документ | docs/06-services/messenger/api/blacklist/list-blacklist.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Инвестор / Агент / Руководитель / Менеджер застройщика / Менеджер ПИН) |
| Auth policy / Permissions | messenger.blacklist.manage (JWT) |
| Scope (RBAC) | платформа (чёрный список — person-level, вне tenant, ADR-0052) |
| Record-level | OwnerUserId = actor — форсится сервером, чужой список недостижим (индекс ix_blacklist_owner) |
| Tenant isolation | Запрос не tenanted: единственная граница — record-level OwnerUserId = actor |
| Audit | read (не sensitive; Reason — приватная заметка владельца, в логи не пишется) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/messenger/blacklist |
| Success status | 200 |
| Idempotency header | не применяется (read-only, безопасный метод) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
OwnerUserId | identity claims | Guid | Да | Required (= actor); из query/тела не принимается | Record-level (own-only) |
Paging | query | PagingQuery | Нет | Take 1..200 (default 20), Offset >= 0 (default 0) | защита от unbounded |
Sort | query | OrderByMethod<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 | Описание |
|---|
BlacklistId | Guid | Да | BlacklistEntity.Id | — | Идентификатор записи (route для unblock-user). |
BlockedUserId | Guid | Да | BlacklistEntity.BlockedUserId | — | Кого заблокировали. |
BlockedDisplayName | string | Да | MessengerUserEntity (FirstName MiddleName LastName) | Фамилия — первой буквой, если нет Accepted-контакта (INV-MU-3) | Отображаемое имя заблокированного. |
Reason | string? | Нет | BlacklistEntity.Reason | — (виден только владельцу — это его список) | Приватная заметка инициатора; null — без причины. |
CreatedAt | DateTimeOffset | Да | BlacklistEntity.CreatedAt | — | Момент блокировки. |
Алгоритм
- Контекст и доступ. Операцию выполняет авторизованный пользователь (
User); OwnerUserId = актор
(из query/тела не принимается — выборка строго по своему списку); требуется право
messenger.blacklist.manage (иначе MESSENGER_BLACKLIST_FORBIDDEN).
- Проверка входа.
Paging.Take ∈ 1..200, Offset >= 0; Sort — известное значение enum
BlacklistSortField; иначе ошибка валидации.
- Выборка. Записи блокировки владельца (
OwnerUserId = actor) с сортировкой по Sort (default
CreatedAt Desc, индексы ix_blacklist_owner/ix_blacklist_created_at), постранично; Total — по той же
выборке. Для страницы — реплики MessengerUserEntity заблокированных
(display-имя + признак Accepted-контакта для снятия маски).
- Маппинг.
BlockedDisplayName собирается из реплики: полное ФИО при наличии Accepted-контакта пары
(Owner↔Blocked), иначе фамилия маскируется первой буквой (INV-MU-3). Отсутствующая реплика (лаг
консюмера) → безопасный fallback («Пользователь»), строка не отбрасывается.
- Результат. Постраничный список
BlacklistItemModel (Items + Total/Take/Offset) → HTTP 200;
пустой Items при пустом списке — тоже 200 (не 404).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | неверные Take/Offset/Sort | «Проверьте параметры списка» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
MESSENGER_BLACKLIST_FORBIDDEN | 403 | нет 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.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы