UC-SEL-003. Аноним открывает подборку по публичной ссылке
Назначение
Описывает сценарий публичного открытия подборки любым актором, включая гостя без авторизации:
подборка привязана к владельцу, но по ссылке (по коду) её может открыть кто угодно. Открытие — по
неперебираемому PublicCode (12 base62, ~71 бит), а не по последовательному id; владелец в публичном
ответе не раскрывается. Read-only сценарий. Связан с контекстом selections.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-SEL-003 |
| Контекст | selections (см. docs/06-services/selections/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Гость (аноним) — базовый случай; любой авторизованный пользователь открывает так же |
| Permissions / scope | permissions не требуются: чтение по 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} фронта |
Основной Поток
- Гость открывает URL
/collection/{publicCode}(возможно, с?ref=— реф-cookie ставит фронт, UC-SEL-002 шаг 5; на этот сценарий не влияет). - Фронт запрашивает
GET /selections/{publicCode}без авторизации (JWT не требуется;X-Anonymous-Idшлётся для трекинга, самимselectionsне используется). - Проверка входа.
publicCode— непустая строка ≤ 16 символов, алфавит base62 ([0-9A-Za-z]); нарушение формата → сразуSELECTIONS_NOT_FOUND(404, без раскрытия правил кода). На 404-ответы действует rate-limit по IP (защита от сканирования — README «Trusted/untrusted boundary»; всплеск 404 — alert). Выключенный feature flagselections.public-open.enabled→ 404 для всех кодов (аварийный режим). - Поиск подборки. Подборка ищется по
PublicCodeсреди не удалённых (DeletedAt == null) — горячий путь по partial-индексуix_selections_public_code_active(SLO p99 < 150 мс), с элементами (граница агрегата, ≤ 200). Не найдена →SELECTIONS_NOT_FOUND(404) — удалённая и несуществующая неразличимы (INV-SEL-009). - Публичная проекция. Строится
PublicSelectionResult:Title,Description,PublicCode,CreatedAt,Itemsв порядкеSortOrder— по каждому элементу (PropertyObjectId,SortOrder,Comment).OwnerUserId,Source,SelectionIdв публичный ответ не включаются (masking — ../domain/selection.md «Permissions / PII»; идентификатор для клиента — самpublicCode).Commentэлементов публичен — владелец предупреждён при заполнении. Состояниеselectionsне меняется — счётчик просмотров не ведётся, просмотры трекаетuser-activity. - Фронт рендерит подборку: заголовок, описание, карточки объектов недвижимости — display-данные (цена, фото,
название, статус) добирает батч-запросом в
catalogпо спискуPropertyObjectId; объекты недвижимости, которых каталог не знает или которые скрыты, отображаются с признаком недоступности (INV-SEL-008 — мёртвые ссылки фильтрует выдача, сама подборка их хранит). - Фронт шлёт события просмотра в
user-activity(гостевая сессияX-Anonymous-Id): открытие страницы подборки и просмотры объектов недвижимости из неё — далее история действий/карточка лида (ADR-0054; зонаuser-activity, не этого контекста). - CTA страницы: «Заявка на презентацию», «Забронировать», переход в карточку объекта недвижимости —
ведут в воронку
catalog/crm(сквозные сценарии UC-1..3).
Альтернативные Потоки
A1. Подборка удалена владельцем (soft delete)
DeletedAt != null→ поиск среди не удалённых подборку не находит → 404SELECTIONS_NOT_FOUND(INV-SEL-009; удалённая неотличима от несуществующей — приватность факта существования).- Фронт показывает «подборка не найдена или удалена владельцем». Завершается.
A2. Неверный/подобранный код
- Код не найден → 404. Подбор бессмыслен: 62^12 ≈ 3×10^21 кодов; rate-limit по IP на 404-ответы + alert «всплеск 404 по кодам» (../README.md «Alerts»). Завершается.
A3. Часть объектов недвижимости подборки недоступна в каталоге
catalogне вернул display-данные для частиPropertyObjectId(объект недвижимости продан/скрыт/удалён у застройщика).- Фронт показывает такие позиции с признаком недоступности (или скрывает — решение UI);
selectionsсостав не модифицирует. Завершается.
A4. Открывает сам владелец
- Тот же публичный query; дополнительно фронт владельца (по совпадению подборки в его
GET /selections) показывает кнопки редактирования — мутации идут через владельческие endpoints с JWT (INV-SEL-002). Публичный ответ владельца не отличается от гостевого. Завершается.
A5. Аварийное отключение публичного доступа
- Feature flag
selections.public-open.enabled = false(абьюз/инцидент) → все публичные открытия получают 404; CRUD владельцев и internal API продолжают работать. Завершается.
A6. Активная подборка без элементов
- Владелец удалил все объекты недвижимости, но подборку не удалял (
DeletedAt == null, состав пуст) — пустая активная подборка допустима (в отличие от snapshot, INV-SNP-004 запрещает пустое только для постов). Публичное открытие находит подборку и возвращает200сItems = [](не 404 — подборка существует). - Фронт рендерит заголовок/описание и пустое состояние состава. Завершается.
Постусловие
- Состояние
selectionsне изменилось; события не публиковались. - Гость увидел актуальное состояние живой подборки (название, описание, комментарии, состав в порядке
SortOrder) без каких-либо данных о владельце. - Просмотр зафиксирован в
user-activity(гостевая сессия) — далее возможна привязка к лиду.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
SELECTIONS_NOT_FOUND | 404 | Код не существует, формат неверен, подборка удалена (A1/A2), флаг выключен (A5) | Страница «подборка не найдена» |
429 | 429 | Превышен rate-limit 404-ответов по IP (A2) | Backoff; для честного клиента недостижимо |
5xx | 500/503 | PostgreSQL недоступен | 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 | Правка живой подборки владельцем сразу видна при повторном открытии по той же ссылке | сквозной тест |
| AC6 | Rate-limit по IP срабатывает на серию 404 (A2) | нагрузочный негативный тест |
| AC7 | Выключенный selections.public-open.enabled → 404 для валидного кода; CRUD владельца работает (A5) | тест feature flag |
| AC8 | Активная подборка с пустым составом открывается 200 с Items = [] (не 404, A6) | граничный тест пустого состава |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (5): UC-NWS-002. Встроить подборку в пост (иммутабельный snapshot), UC-SEL-001. Создание подборки из текущего избранного, UC-SEL-002. Шаринг подборки по ссылке (SharePopover + реферальная ссылка), UC-SEL-004. Автор поста вставляет подборку в пост (иммутабельный snapshot блока), UC-SEL-005. Агент отправляет подборку клиенту в лиде/сделке
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- 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.