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

UC-SEL-003. Аноним открывает подборку по публичной ссылке

Назначение

Описывает сценарий публичного открытия подборки любым актором, включая гостя без авторизации: подборка привязана к владельцу, но по ссылке (по коду) её может открыть кто угодно. Открытие — по неперебираемому PublicCode (12 base62, ~71 бит), а не по последовательному id; владелец в публичном ответе не раскрывается. Read-only сценарий. Связан с контекстом selections.

Metadata

ПолеЗначение
IDUC-SEL-003
Контекстselections (см. docs/06-services/selections/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольГость (аноним) — базовый случай; любой авторизованный пользователь открывает так же
Permissions / scopepermissions не требуются: чтение по PublicCode — публичная операция (INV-SEL-002, читающая часть); feature flag selections.public-open.enabled может аварийно закрыть доступ (../README.md «Feature flags»)
Tenantне tenanted (ADR-0052)
Record-levelне применяется к чтению: знание PublicCode = право чтения; мутации по-прежнему только владельцу

Предусловие

  • Получатель знает ссылку /collection/{publicCode} — получил её шарингом (UC-SEL-002), от агента в лиде (UC-SEL-005) или из поста (UC-SEL-004 — там ссылка «открыть живую подборку» из SourceSelectionPublicCode).
  • Подборка существует и не удалена (DeletedAt == null, INV-SEL-009).

Триггер

ПолеЗначение
ИсточникПереход по URL (внешний канал / пост / чат)
Триггерящий элементСтраница /collection/{publicCode} фронта

Основной Поток

  1. Гость открывает URL /collection/{publicCode} (возможно, с ?ref= — реф-cookie ставит фронт, UC-SEL-002 шаг 5; на этот сценарий не влияет).
  2. Фронт запрашивает GET /selections/{publicCode} без авторизации (JWT не требуется; X-Anonymous-Id шлётся для трекинга, самим selections не используется).
  3. Проверка входа. publicCode — непустая строка ≤ 16 символов, алфавит base62 ([0-9A-Za-z]); нарушение формата → сразу SELECTIONS_NOT_FOUND (404, без раскрытия правил кода). На 404-ответы действует rate-limit по IP (защита от сканирования — README «Trusted/untrusted boundary»; всплеск 404 — alert). Выключенный feature flag selections.public-open.enabled → 404 для всех кодов (аварийный режим).
  4. Поиск подборки. Подборка ищется по PublicCode среди не удалённых (DeletedAt == null) — горячий путь по partial-индексу ix_selections_public_code_active (SLO p99 < 150 мс), с элементами (граница агрегата, ≤ 200). Не найдена → SELECTIONS_NOT_FOUND (404) — удалённая и несуществующая неразличимы (INV-SEL-009).
  5. Публичная проекция. Строится PublicSelectionResult: Title, Description, PublicCode, CreatedAt, Items в порядке SortOrder — по каждому элементу (PropertyObjectId, SortOrder, Comment). OwnerUserId, Source, SelectionId в публичный ответ не включаются (masking — ../domain/selection.md «Permissions / PII»; идентификатор для клиента — сам publicCode). Comment элементов публичен — владелец предупреждён при заполнении. Состояние selections не меняется — счётчик просмотров не ведётся, просмотры трекает user-activity.
  6. Фронт рендерит подборку: заголовок, описание, карточки объектов недвижимости — display-данные (цена, фото, название, статус) добирает батч-запросом в catalog по списку PropertyObjectId; объекты недвижимости, которых каталог не знает или которые скрыты, отображаются с признаком недоступности (INV-SEL-008 — мёртвые ссылки фильтрует выдача, сама подборка их хранит).
  7. Фронт шлёт события просмотра в user-activity (гостевая сессия X-Anonymous-Id): открытие страницы подборки и просмотры объектов недвижимости из неё — далее история действий/карточка лида (ADR-0054; зона user-activity, не этого контекста).
  8. CTA страницы: «Заявка на презентацию», «Забронировать», переход в карточку объекта недвижимости — ведут в воронку catalog/crm (сквозные сценарии UC-1..3).

Альтернативные Потоки

A1. Подборка удалена владельцем (soft delete)

  1. DeletedAt != null → поиск среди не удалённых подборку не находит → 404 SELECTIONS_NOT_FOUND (INV-SEL-009; удалённая неотличима от несуществующей — приватность факта существования).
  2. Фронт показывает «подборка не найдена или удалена владельцем». Завершается.

A2. Неверный/подобранный код

  1. Код не найден → 404. Подбор бессмыслен: 62^12 ≈ 3×10^21 кодов; rate-limit по IP на 404-ответы + alert «всплеск 404 по кодам» (../README.md «Alerts»). Завершается.

A3. Часть объектов недвижимости подборки недоступна в каталоге

  1. catalog не вернул display-данные для части PropertyObjectId (объект недвижимости продан/скрыт/удалён у застройщика).
  2. Фронт показывает такие позиции с признаком недоступности (или скрывает — решение UI); selections состав не модифицирует. Завершается.

A4. Открывает сам владелец

  1. Тот же публичный query; дополнительно фронт владельца (по совпадению подборки в его GET /selections) показывает кнопки редактирования — мутации идут через владельческие endpoints с JWT (INV-SEL-002). Публичный ответ владельца не отличается от гостевого. Завершается.

A5. Аварийное отключение публичного доступа

  1. Feature flag selections.public-open.enabled = false (абьюз/инцидент) → все публичные открытия получают 404; CRUD владельцев и internal API продолжают работать. Завершается.

A6. Активная подборка без элементов

  1. Владелец удалил все объекты недвижимости, но подборку не удалял (DeletedAt == null, состав пуст) — пустая активная подборка допустима (в отличие от snapshot, INV-SNP-004 запрещает пустое только для постов). Публичное открытие находит подборку и возвращает 200 с Items = [] (не 404 — подборка существует).
  2. Фронт рендерит заголовок/описание и пустое состояние состава. Завершается.

Постусловие

  • Состояние selections не изменилось; события не публиковались.
  • Гость увидел актуальное состояние живой подборки (название, описание, комментарии, состав в порядке SortOrder) без каких-либо данных о владельце.
  • Просмотр зафиксирован в user-activity (гостевая сессия) — далее возможна привязка к лиду.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
SELECTIONS_NOT_FOUND404Код не существует, формат неверен, подборка удалена (A1/A2), флаг выключен (A5)Страница «подборка не найдена»
429429Превышен rate-limit 404-ответов по IP (A2)Backoff; для честного клиента недостижимо
5xx500/503PostgreSQL недоступенRetry; ссылки из постов/CRM временно не открываются (criticality low)

Доменные События

Не поднимаются — read-only сценарий (изменений состояния нет).

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
— (сценарий не публикует; трекинг просмотра — события user-activity, вне этого контекста)

Acceptance Criteria

#КритерийПроверка
AC1Гость без JWT открывает подборку по коду; авторизованный — тот же ответинтеграционный тест обоих акторов
AC2Публичный ответ не содержит OwnerUserId, Source, SelectionIdконтрактный тест проекции
AC3Удалённая подборка → 404, ответ неотличим от несуществующего кода (A1, INV-SEL-009)негативный тест + сравнение ответов
AC4Элементы возвращаются в порядке SortOrder 0..N-1 с комментариямиинтеграционный тест
AC5Правка живой подборки владельцем сразу видна при повторном открытии по той же ссылкесквозной тест
AC6Rate-limit по IP срабатывает на серию 404 (A2)нагрузочный негативный тест
AC7Выключенный selections.public-open.enabled → 404 для валидного кода; CRUD владельца работает (A5)тест feature flag
AC8Активная подборка с пустым составом открывается 200 с Items = [] (не 404, A6)граничный тест пустого состава

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

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

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

  • API метод: ../api/public/get-selection-by-code.md (будущий путь по конвенции, api-method-template).
  • Domain entity: ../domain/selection.md (PublicCode, INV-SEL-002/005/008/009).
  • Как ссылка попадает к гостю: UC-SEL-002, UC-SEL-005; из поста — UC-SEL-004.
  • Display-данные объектов недвижимости: docs/06-services/catalog/README.md (будущий путь); трекинг просмотров: docs/06-services/user-activity/README.md (будущий путь, ADR-0054).
  • Решения: ADR-0052, ADR-0056.