GET /api/investors/me/referral-links/lookup/{id} — Резолв реф-ссылки по id
Назначение
Точечно резолвит одну собственную реферальную ссылку по её id в облегчённое lookup-представление
ReferralLinkLookupEntry. Нужен, чтобы отобразить выбранное значение в выпадающем списке на фронте:
когда у контрола есть только сохранённый id ссылки, но нужно показать её url/описание без загрузки
всего списка (lookup-referral-links.md).
| Поле | Значение |
|---|
| Сервис/контекст | referral |
| API area | public |
| Feature group | referral-links |
| Статус | draft |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (авторизованный пользователь) |
| Auth policy / Permissions | referral.links.view-self |
| Scope | self (роль не требуется) |
| Record-level | фильтр OwnerUserId = UserId актора; чужая ссылка неотличима от несуществующей (нет enumeration по id, INV-REF-L4) |
| Tenant isolation | не применяется (record-level по владельцу, ADR-0052) |
| Audit | none (несенситивное чтение собственных данных) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/investors/me/referral-links/lookup/{id} |
| Success status | 200 |
| Idempotency | не применяется (safe GET) |
| Correlation | OpenTelemetry trace (traceparent) |
Маршрутизация: статический сегмент lookup имеет приоритет над route-параметром {id} уровня
/referral-links/{id} — коллизий нет.
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT (подпись, exp, notBefore) | 401 при отсутствии/просрочке |
id | route | Guid | Да | id != Guid.Empty; принадлежность не доверяем | record-level guard OwnerUserId = UserId; чужой id → REFERRAL_LINK_NOT_FOUND |
Модель ответа ReferralLinkLookupEntry
Результат — одиночная item-модель (не список). Дом модели —
lookup-referral-links.md; ниже — проекция полей из
referral-link.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
id | string (Guid) | Да | ReferralLinkEntity.Id | — | Идентификатор ссылки. |
url | string | Да | ReferralLinkEntity.Url | — | Slug (lowercase); полный URL собирает фронт. |
description | string? | Нет | ReferralLinkEntity.Description | — | Описание канала/назначения ссылки. |
isDefault | bool | Да | ReferralLinkEntity.IsDefault | — | Признак ссылки «по умолчанию» владельца (INV-REF-L2). |
channel | ReferralChannel (string enum)? | Нет | ReferralLinkEntity.Channel | — | Канал распространения (unknown при чтении → fallback Other, referral-link.md). |
Предусловия и постусловия
- Предусловия. Пользователь авторизован. Резолвится только действующая (не удалённая) собственная
ссылка.
- Постусловия. Состояние не меняется (чтение);
VisitsCount не инкрементируется (перехода по
ссылке нет — счётчик растит только validate-referral-link.md); события не
публикуются.
Алгоритм
- Контекст и доступ. Операцию выполняет авторизованный пользователь;
UserId — из claim sub
(иначе 401); id — из route; требуется право referral.links.view-self.
- Проверка входа.
id != Guid.Empty — иначе ValidationError.
- Что читаем. Собственную ссылку по
Id = id и OwnerUserId = UserId, не удалённую
(DeletedAt == null). Отсутствие / чужая / soft-deleted ссылка неразличимы и дают
REFERRAL_LINK_NOT_FOUND (INV-REF-L4: без раскрытия существования). Удалённая ссылка не
резолвится — согласовано с referral-link.md.
- Маппинг. Поле-в-поле найденной ссылки → entry:
id, url, description, isDefault,
channel.
- Результат.
ReferralLinkLookupEntry → HTTP 200; при отсутствии — REFERRAL_LINK_NOT_FOUND
(HTTP 404, ProblemDetails). Консистентность — read-your-writes (основная PostgreSQL).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
REFERRAL_LINK_NOT_FOUND | 404 | id не существует / чужая ссылка / soft-deleted (INV-REF-L4, без раскрытия существования) | «Ссылка не найдена» | no retry |
ValidationError | 400 | id == Guid.Empty | список ошибок полей | исправить вход |
| — (auth) | 401 | Нет/просрочен/невалиден JWT | без тела схемы | re-login |
Критерии приёмки
- Given авторизованный владелец и
id его действующей ссылки, When резолв по id, Then
200 с ReferralLinkLookupEntry (id, url, description, isDefault, channel).
- Given
id чужой ссылки, When резолв, Then REFERRAL_LINK_NOT_FOUND (404) — чужая
ссылка неотличима от несуществующей (INV-REF-L4, без enumeration).
- Given
id soft-deleted собственной ссылки, When резолв, Then REFERRAL_LINK_NOT_FOUND
(404).
- Given
id == Guid.Empty, When резолв, Then ValidationError (400).
- Given отсутствует/просрочен токен, When резолв, Then
401.
Совместимость и наблюдаемость
- Контракт стабилен; additive: у
ReferralLinkLookupEntry — optional channel (ReferralChannel),
без breaking change; unknown enum при чтении → fallback Other + warning
(referral-link.md). Удаление/переименование полей — breaking
(migration + rollout).
- Logs:
UserId, id, outcome=found|not_found — без PII; metrics:
referral_link_lookup_by_id_total{outcome}, latency p95; traces — OpenTelemetry. Dashboard —
referral-overview; алерт на p95 > 300 мс (SLO public API, README.md). Всплеск
outcome=not_found от одного актора — индикатор перебора чужих id (record-level guard INV-REF-L4
скрывает существование, но метрика полезна для rate-based-детекции).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы