GET /api/admin/referrals/links/{linkId} — Одна реферальная ссылка (admin, getReferralLinkById)
Назначение
Детальная admin-карточка одной реферальной ссылки: по linkId вернуть ссылку с данными владельца
(ФИО, телефон) и числом регистраций. Кросс-tenant чтение: администратор платформы видит любую ссылку
независимо от TenantId владельца. Дом item-модели AdminReferralLinkEntry — соседняя списочная
операция get-referral-links.md; здесь та же модель под detail-карточку. Общий
конверт и модели — README.md.
| Поле | Значение |
|---|
| Сервис/контекст | referral |
| API area | admin |
| Feature group | admin-referrals |
| Статус | approved |
| Документ | docs/06-services/referral/api/admin/get-referral-link-by-id.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) (администратор платформы) |
| Auth policy / Permissions | permission referral.registrations.view-all (каталог — permission.md) |
| Scope (RBAC) | платформа (кросс-tenant) |
| Tenant isolation | кросс-tenant по платформенному permission (tenant-предикат не накладывается); включая soft-deleted ссылки — admin-журнал |
| Audit | read-sensitive: доступ к PII владельца (ownerName, ownerPhoneNumber) — access-audit |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/admin/referrals/links/{linkId} |
| Success status | 200 |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
AdminUserId | identity claims (sub) | Guid | Да | валидный токен + permission referral.registrations.view-all | 401/403 |
linkId | route | Guid | Да | непустой; существование — шаг 3 | — |
Модель ответа AdminReferralLinkEntry
Контрактный дом модели — списочная операция get-referral-links.md; здесь та же
item-модель под detail-карточку. Источники полей — агрегат referral-link.md
и реплика ReferralUserReplica (attach-rules.md, раздел «Реплика»);
FullName — README.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
id | string (Guid) | Да | ReferralLinkEntity.Id | — | Идентификатор ссылки. |
ownerUserId | string (Guid) | Да | ReferralLinkEntity.OwnerUserId | — | Владелец. |
ownerName | FullName | Да | ReferralUserReplica.Name | — | ФИО владельца (README.md); пустые строки при lazy-регистрации. |
ownerPhoneNumber | string? | Нет | ReferralUserReplica.PhoneNumber | лог +7***1234 | Телефон владельца (E.164). PII, только admin; отдаётся в открытом виде. |
url | string | Да | ReferralLinkEntity.Url | — | Slug ссылки. |
description | string? | Нет | ReferralLinkEntity.Description | — | Описание канала/назначения. |
isDefault | bool | Да | ReferralLinkEntity.IsDefault | — | Ссылка «по умолчанию» владельца. |
channel | ReferralChannel (enum строкой) | Да | ReferralLinkEntity.Channel | — | Канал; дом enum — referral-link.md. |
registrationsCount | number (int) | Да | computed (производный — referral-link.md) | — | Число регистраций по ссылке. |
createdAt | string (ISO 8601) | Да | ReferralLinkEntity.CreatedAt | — | Момент создания. |
email | string? | Нет | всегда null | — | Email в реплику не реплицируется (PII-минимизация) → всегда null. |
Алгоритм
- Контекст и доступ. Оператор — администратор платформы; требуется permission
referral.registrations.view-all. Доступ кросс-tenant, включая soft-deleted ссылки (admin-журнал).
- Проверка входа.
linkId непустой валидный Guid, иначе ValidationError.
- Выдача. Ищется ссылка по
linkId (кросс-tenant, включая удалённые); если не найдена →
REFERRAL_LINK_NOT_FOUND. К найденной ссылке присоединяются ФИО/телефон владельца из реплики
(реплика ещё не догнала событие identity → ownerName пустой FullName, ownerPhoneNumber = null,
fail-soft) и число регистраций (registrationsCount — live COUNT по журналу, денормализованного
счётчика нет: отражает все зафиксированные регистрации на момент чтения и совпадает с meta.total
журнала ссылки get-link-registrations.md); email всегда null;
channel — enum строкой (unknown при чтении → Other + warning). Телефон оператору отдаётся в
открытом виде, в логах маскируется; факт доступа к PII фиксируется в access-audit.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | linkId пустой / не Guid | перечень полей | исправить |
REFERRAL_LINK_NOT_FOUND | 404 | ссылки с таким linkId не существует (в т.ч. кросс-tenant) | «Ссылка не найдена» | no retry |
| — (auth) | 401 | нет/невалиден токен | «Требуется авторизация» | re-login |
| — (authz) | 403 | нет permission referral.registrations.view-all | «Недостаточно прав» | no retry |
Постусловие
Состояние не изменяется (чистое чтение): ссылка, счётчики и реплика не трогаются, события не
публикуются. Побочный факт — access-audit «оператор просмотрел карточку ссылки linkId с PII владельца».
Acceptance Criteria
- Given администратор с permission
referral.registrations.view-all и существующая ссылка,
When запрашивает GET …/links/{linkId}, Then 200 c AdminReferralLinkEntry: ownerName/
ownerPhoneNumber из реплики, registrationsCount — число регистраций, channel — enum строкой,
email = null; телефон в логах маскирован, доступ к PII — в access-audit.
- Given ссылка принадлежит другому tenant или soft-deleted, When запрос, Then
200 — доступ
кросс-tenant и к удалённым (admin-журнал).
- Given
linkId не существует, When запрос, Then 404 REFERRAL_LINK_NOT_FOUND.
- Given реплика владельца ещё не догнала событие identity, When запрос, Then
ownerName —
пустой FullName, ownerPhoneNumber = null (fail-soft), ссылка всё равно отдаётся.
- Given в хранилище неизвестное значение
Channel (после additive-расширения), When чтение,
Then channel = Other + warning в логах (без ошибки клиенту).
- Given актор без permission, When запрос, Then
403; без токена — 401; linkId не Guid —
400 ValidationError.
Совместимость и наблюдаемость
- Контракт стабилен. Новые optional поля (в т.ч.
channel) — без breaking change; удаление/
переименование полей — breaking (migration + rollout).
- Logs:
AdminUserId, linkId, ownerUserId (телефон — маской +7***1234, ФИО в лог не пишется);
metrics: referral_admin_link_by_id_requests_total{result}, latency p95; traces — сквозной trace
через traceparent; access-audit; runbook — referral/README.md (Runtime).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы