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

GET /api/admin/referrals/links/{linkId}/registrations — Журнал ссылки с PII (admin getLinkRegistrations)

Назначение

Административный постраничный журнал регистраций по одной реферальной ссылке с персональными данными приглашённых (ФИО, телефон): проекция append-only журнала ReferralRegistrationEntity. В отличие от обезличенной public-выдачи владельца ../public/get-link-registrations.md (только referredInvestorId), администратор платформы видит ФИО/телефон приглашённого из реплики пользователей ReferralUserReplica. Используется в разборе привязок, поддержке и подготовке коррекций связей. Общий конверт, paging- и FullName-модели — README.md.

Metadata

ПолеЗначение
Сервис/контекстreferral
API areaadmin
Feature groupreferral-registrations
Статусapproved
Документdocs/06-services/referral/api/admin/get-link-registrations.md

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

ПроверкаЗначение
Actor typeOperator(Admin) (администратор платформы)
Auth policy / Permissionsauthenticated; permission referral.registrations.view-all (permission.md)
Scope (RBAC)платформа (cross-tenant): admin видит журнал любой ссылки
Tenant isolationкросс-tenant по платформенному permission (tenant-предикат не накладывается)
Auditread-sensitive: выдача PII (ФИО/телефон) пишется в access-audit

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/admin/referrals/links/{linkId}/registrations
Success status200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
OperatorIdidentity claims (sub)GuidДавалидный токен + permission referral.registrations.view-all401 / 403
linkIdrouteGuidДанепустой; существующая ссылка (шаг 3)
OffsetqueryintДаmin 0, max 2147483647
TakequeryintНет (admin)min 1, max 1000; клиент админки передаёт явноограничение размера выдачи

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

Контейнер offset-paging (дом paging-моделей — README.md):

ПолеТипОбязательностьИсточникMaskingОписание
metaPagingResultMetaДаcomputedtotal / take / offsetREADME.md.
itemsAdminReferralRegistrationEntry[]Даprojection + joinСтраница записей журнала ссылки.

Модель AdminReferralRegistrationEntry (дом item-модели)

Проекция журнала ReferralRegistrationEntity + denormalized-поля + данные приглашённого из реплики ReferralUserReplica:

ПолеТипОбязательностьИсточникMaskingОписание
registeredAtstring (ISO 8601)ДаReferralRegistrationEntity.RegisteredAtМомент регистрации/привязки (сортировка по нему desc).
referralLinkIdstring (Guid)ДаReferralRegistrationEntity.ReferralLinkIdСсылка записи (= linkId запроса).
linkUrlstringДаReferralRegistrationEntity.LinkUrl (денормализация)Исторический slug на момент записи; переживает переименование/удаление ссылки.
referredInvestorIdstring (Guid)ДаReferralRegistrationEntity.ReferredUserIdПриглашённый пользователь.
nameFullNameДаReferralUserReplica.Name (по ReferredUserId)PII (raw для admin)ФИО приглашённого из реплики (README.mdFullName); реплики нет (лаг consumer-а) → пустые строки.
phoneNumberstring?НетReferralUserReplica.PhoneNumberPII; в логах маска +7***1234Телефон приглашённого (E.164); null, если реплики ещё нет.
emailstring?Нетвсегда nullEmail в реплику не реплицируется (PII-минимизация, attach-rules.md «Реплика») → всегда null.
registrationTypestring (ReferralRegistrationType)ДаReferralRegistrationEntity.RegistrationTypeInitial / Rebind (enum строкой, referral-registration.md).

Алгоритм

  1. Контекст и доступ. Оператор — администратор платформы; требуется permission referral.registrations.view-all (иначе 403). Доступ кросс-tenant.
  2. Проверка входа. linkId непустой, Offset >= 0, 1 <= Take <= 1000; иначе ValidationError.
  3. Проверка ссылки. Ссылка должна существовать (журнал включает soft-deleted ссылки — история сохраняется); если ссылки по linkId нет → REFERRAL_LINK_NOT_FOUND (чтобы не отдавать пустую страницу как валидный результат по несуществующему id).
  4. Выдача. Возвращается страница записей журнала по ссылке (ReferralLinkId == linkId), упорядоченная по дате регистрации RegisteredAt desc, при равных значениях — по Id (стабильный tie-break; правило порядка/консистентности — README.md). Журнал append-only: исторические страницы стабильны, новые записи (Initial при привязке, Rebind при admin-коррекции) появляются только в «голове» ленты. К каждой записи присоединяются ФИО/телефон приглашённого из реплики (реплика отсутствует, лаг consumer-а → name пустой FullName, phoneNumber = null, деградация, не ошибка); email всегда null. PII отдаётся сырой (permission referral.registrations.view-all), в логах телефон маскируется. meta содержит полное число записей под фильтром, размер и смещение страницы.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/невалиден токен«Требуется авторизация»re-login
— (policy)403Нет permission referral.registrations.view-all«Недостаточно прав»no retry
ValidationError400linkId/Offset/Take вне ограниченийперечень полейисправить
REFERRAL_LINK_NOT_FOUND404Ссылка по linkId не существует«Реферальная ссылка не найдена»no retry

Реплика приглашённого отсутствует (consumer lag) — не ошибка: name/phoneNumber деградируют, запись журнала всё равно возвращается.

Постусловие

Состояние не изменяется (append-only журнал только читается): записи, ссылка и реплика не трогаются, события не публикуются. Побочный факт — access-audit «оператор просмотрел журнал ссылки linkId с PII».

Acceptance Criteria

  • Given администратор с permission referral.registrations.view-all и существующая ссылка с регистрациями, When запрашивает GET …/links/{linkId}/registrations, Then 200 со страницей AdminReferralRegistrationEntry, упорядоченной по registeredAt desc; в теле — ФИО/телефон приглашённого (raw), linkUrl — исторический slug на момент записи, registrationType ∈ {Initial, Rebind}; телефон в логах маскирован, email = null.
  • Given ссылка soft-deleted, но с историческими записями, When запрос, Then 200 — журнал переживает удаление ссылки (записи возвращаются).
  • Given linkId не существует ни у одной ссылки, When запрос, Then 404 REFERRAL_LINK_NOT_FOUND (пустая страница по несуществующему id не выдаётся как валидный результат).
  • Given реплика приглашённого отстаёт от журнала, When запрос, Then запись возвращается с пустым name и phoneNumber = null (graceful degradation).
  • Given актор без permission, When запрос, Then 403; без токена — 401.
  • Given linkId/Offset/Take вне ограничений, When запрос, Then 400 ValidationError.
  • Given в журнале записи с равным registeredAt, When постраничный обход, Then порядок стабилен по вторичному ключу Id; append-only журнал — исторические страницы не смещаются, новые записи появляются только в «голове» ленты (README.md).

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

  • Контракт стабилен (имена полей AdminReferralRegistrationEntry не меняются). Удаление/ переименование/смена типа поля — breaking (migration + rollout admin-клиента).
  • Unknown значение RegistrationType при чтении — трактуется консервативно (лог warning), в выдачу идёт как есть строкой.
  • Logs: OperatorId, linkId, размер страницы, факт выдачи PII (audit read-sensitive); телефоны — маской +7***1234. Metrics: referral_admin_link_registrations_requests_total, latency p95. Traces — сквозной trace через traceparent. Dashboard/runbook — ../README.md (Runtime).

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

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

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