GET /api/admin/referrals/investors/{investorId}/referrals — Список приглашённых реферером (getInvestorReferrals, admin)
Назначение
Постраничный список всех пользователей, которых привёл конкретный реферер (investorId в route — это
реферер, чьи приглашённые запрашиваются), для администратора платформы: разбор реферальных связей
«кто кого привёл», сверка оснований будущих выплат, обработка претензий. Выдача содержит PII
приглашённого (ФИО/телефон), поэтому доступна только по платформенному permission
referral.registrations.view-all. Общий конверт и paging-модели — README.md.
| Поле | Значение |
|---|
| Сервис/контекст | referral |
| API area | admin |
| Feature group | referral-bonds |
| Статус | approved |
| Документ | docs/06-services/referral/api/admin/get-investor-referrals.md |
| Assumptions | Фильтр статуса связей — открытый вопрос: по умолчанию отдаётся Status = Active, доступ к истории (Replaced/Revoked) — через additive query-параметр в дальнейшем (см. «Совместимость»). |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) (администратор платформы) |
| Auth policy / Permissions | authenticated admin; permission referral.registrations.view-all (permission.md) |
| Scope (RBAC) | платформа (кросс-tenant admin-выдача) |
| Audit | read-sensitive: access-audit «кто/когда/какого реферера просматривал»; телефоны в логах маскируются +7***1234 |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/admin/referrals/investors/{investorId}/referrals |
| Success status | 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
OperatorId | identity claims (sub) | Guid | Да | валидный токен + permission referral.registrations.view-all | 401 / 403; audit-стамп «кто просматривал» |
investorId | route | Guid | Да | непустой; реферер (ReferralBondEntity.ReferrerUserId), не приглашённый | logical id, не PII |
Offset | query | int | Да | min 0, max 2147483647 | — |
Take | query | int | Нет (admin-клиент передаёт явно) | min 1, max 1000 | ограничение размера выдачи |
Модель ответа AdminReferredInvestorEntryPagingResult
Контейнер offset-paging; базовые meta (total/take/offset) — дом README.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
meta | PagingResultMeta | Да | projection | — | Метаданные страницы (README.md). |
items | AdminReferredInvestorEntry[] | Да | projection | — | Приглашённые реферером на странице. |
Модель AdminReferredInvestorEntry (дом item-модели)
Проекция ReferralBondEntity (id/дата/ссылка — referral-bond.md) +
данные приглашённого из реплики ReferralUserReplica (ФИО/телефон —
attach-rules.md, раздел «Реплика»); FullName — README.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
referredInvestorId | string (Guid) | Да | ReferralBondEntity.ReferredUserId | — | Id приглашённого. |
referredUserId | string (Guid) | Да | ReferralBondEntity.ReferredUserId | — | Псевдоним поля (тот же id; владелец — любой пользователь). |
name | FullName | Да | ReferralUserReplica.Name (по ReferredUserId) | — | ФИО приглашённого; при lazy-регистрации firstName/lastName — пустые строки. |
phoneNumber | string? | Нет | ReferralUserReplica.PhoneNumber | +7***1234 в логах | Телефон приглашённого (E.164); null, если реплика отстаёт от связи. |
email | string? | Нет | всегда null | — | Email в реплику не реплицируется (PII-минимизация) → всегда null. |
boundAt | string (ISO 8601) | Да | ReferralBondEntity.BoundAt | — | Момент привязки. |
referralLinkId | string (Guid) | Да | ReferralBondEntity.ReferralLinkId | — | Ссылка, через которую произошла привязка. |
Алгоритм
- Контекст и доступ. Оператор — администратор платформы; требуется permission
referral.registrations.view-all (иначе REFERRAL_FORBIDDEN). Доступ кросс-tenant; факт просмотра
пишется в access-audit.
- Проверка входа.
investorId непустой, Offset >= 0, 1 <= Take <= 1000; иначе
ValidationError. Существование реферера отдельно не проверяется (несуществующий/пустой реферер →
пустая страница, шаг 3).
- Выдача. Возвращается страница связей, где
investorId выступает реферером
(ReferrerUserId == investorId, по умолчанию Status = Active — открытый вопрос), упорядоченная
по дате привязки BoundAt desc, при равных значениях — по Id (стабильный tie-break; правило
порядка/консистентности страницы — README.md). К каждой связи присоединяются
ФИО/телефон приглашённого из реплики (реплика отсутствует, отстаёт от связи → name пустой
FullName, phoneNumber = null, graceful degradation); email всегда null. PII — только в теле
ответа, в логах телефон маскируется. Пустой/несуществующий реферер → meta.total = 0, items = [].
meta содержит полное число записей под фильтром, размер и смещение страницы. Между запросами
соседних страниц статус связи может смениться конкурентной admin-коррекцией
(Active → Replaced/Revoked, rebind-bond.md/revoke-bond.md) —
при фильтре Status = Active такая связь исчезает из последующих страниц (offset-снимок,
README.md); чистое чтение конфликтов не создаёт.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | investorId пустой или Offset/Take вне диапазона | перечень полей | исправить |
| — (auth) | 401 | Нет/невалиден токен | «Требуется авторизация» | re-login |
REFERRAL_FORBIDDEN | 403 | Отсутствует permission referral.registrations.view-all | «Недостаточно прав» | no retry |
Несуществующий/пустой реферер — не ошибка: 200 с пустой страницей (total = 0).
Постусловие
Состояние не изменяется (чистое чтение): связи, журнал и реплика не трогаются, события не публикуются.
Единственный побочный факт — запись access-audit «оператор просмотрел приглашённых реферера investorId».
Acceptance Criteria
- Given администратор с permission
referral.registrations.view-all и реферер с активными связями,
When запрашивает GET …/investors/{investorId}/referrals, Then 200 со страницей
AdminReferredInvestorEntry, упорядоченной по boundAt desc; в теле — ФИО/телефон приглашённого, в
логах телефон маскирован +7***1234, email = null; факт доступа к PII — в access-audit.
- Given реферер без связей или несуществующий
investorId, When запрос, Then 200 с
meta.total = 0 и items = [] (не ошибка; существование реферера отдельно не проверяется).
- Given приглашённый есть в связи, но его реплика
ReferralUserReplica ещё не доставлена (лаг
consumer-а pin.identity.users.v1), When запрос, Then запись всё равно в выдаче: name —
пустой FullName, phoneNumber = null (graceful degradation, не ошибка).
- Given актор без permission
referral.registrations.view-all, When запрос, Then
403 REFERRAL_FORBIDDEN; без токена — 401.
- Given
Offset < 0 или Take вне 1..1000, When запрос, Then 400 ValidationError с
перечнем полей.
- Given реферер с историей коррекций (
Replaced/Revoked), When запрос без доп. параметров,
Then выдаются только связи Status = Active (доступ к неактивным — будущий additive-параметр).
- Given у реферера несколько связей с одинаковым
boundAt, When постраничный обход, Then
порядок детерминирован вторичным ключом Id — записи не дублируются и не пропадают между страницами
(стабильный tie-break, README.md).
- Given между запросами страниц связь отзывается/заменяется admin-коррекцией, When следующая
страница, Then при фильтре
Status = Active эта связь в неё уже не попадает (offset-снимок не
транзакционен между страницами).
Совместимость и наблюдаемость
- Контракт стабилен; удаление/переименование полей item-а — breaking (migration + rollout).
- Открытый вопрос — фильтр статуса связей: по умолчанию
Status = Active, доступ к
Replaced/Revoked для разбора выплат — через additive query-параметр (напр. IncludeInactive)
без breaking change.
- Logs:
investorId, размер страницы, total, факт выдачи PII (access-audit; телефоны — маской
+7***1234, ФИО в лог не пишется); metrics: referral_admin_investor_referrals_requests_total,
latency p95; traces — сквозной trace через traceparent; dashboard — referral-overview
(../README.md, Runtime).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы