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

Catalog Public API — реестр методов и общие правила

Реестр public-методов витрины каталога (контекст catalog, сервис Pin.Catalog.Api, area public) и общие правила этой области: модель акторов и гейт ленты, правила маскирования для гостя, keyset-пагинация ленты, сортировки, общие response-модели. Документы методов ссылаются сюда и описывают только свою дельту. Дома полей — ../domain/.

Metadata

ПолеЗначение
Сервис/контекстcatalog
API areapublic
Статусdraft
OwnerPIN Marketplace
Область действиялента/поиск/фильтры/сортировки/карточки/ассортимент; developers- и admin-области — отдельные реестры (api/developers/, api/admin/)
Assumptionsреф-cookie referralUrl ставится фронтом (referral, cookie 30 дней)

Реестр методов

МетодRouteАктор (мин.)ГейтНазначениеДокумент
GET/api/v1/projectsGuestнет (маскирование)Лента проектов (главная, /hotels)get-projects.md
GET/api/v1/projects/{id}Guestнет (ограниченная) / JWT‖реф-cookie (полная)Карточка проектаget-project.md
GET/api/v1/projects/{id}/lotsRefGuestJWT или реф-cookieАссортимент проекта/отеляget-project-property-objects.md
GET/api/v1/lotsRefGuestJWT или реф-cookieЛента объектов недвижимости /lotsget-property-objects.md
GET/api/v1/lots/{id}Guestнет (ограниченная) / JWT‖реф-cookie (полная)Карточка объекта недвижимостиget-property-object.md
GET/api/v1/catalog/filtersGuestнетLookup справочников значений фильтровget-catalog-filters.md

Ошибки — ProblemDetails с машинным полем code.

Модель акторов и гейт ленты (GATE-FEED)

Класс актора определяется до выполнения бизнес-логики:

Класс актораУсловиеЧто видит
Userвалидный Bearer JWT (подпись, exp, notBefore)полные карточки; агент с permission catalog.commissions.view — дополнительно комиссии
RefGuestJWT отсутствует, cookie referralUrl непустая (формат — валидный URL ≤ 2048 символов; глубокая валидация ссылки — контекст referral, каталог синхронно не вызывает — simplicity-first, ADR-0056)полные карточки и ленты, без комиссий; инвест-калькуляторы на фронте — readOnly
Guestнет JWT и нет cookieтолько методы без гейта, с маскированием MASK-*

Правила:

  • GATE-FEED: методы с гейтом (/lots, /projects/{id}/lots, полные карточки) требуют User или RefGuest; Guest401 (фронт открывает auth-модалку или предлагает реферальную ссылку).
  • Невалидный/просроченный JWT — всегда 401 (фронт очищает сессию); fallback на cookie не выполняется (протухший токен ≠ гость).
  • Классы актора — только правило видимости ответа; записей о госте каталог не создаёт (сессии/anonymous_id — контекст user-activity, ADR-0054).

Правила маскирования

КодКому применяетсяПравило
MASK-PRICEGuest (лента проектов, ограниченная карточка проекта)Точные цены скрываются: priceRange.min округляется вниз до 100 000 ₽, priceRange.maxвверх до 100 000 ₽; pricePerMeternull. Принятое расхождение: ограниченная карточка объекта недвижимости показывает точную цену (явно предусмотрено для ограниченной карточки объекта).
MASK-STATUSGuestСтатусы объектов скрываются: propertyObjectStatusnull, propertyObjectsAvailablenull (остаётся propertyObjectsTotal).
MASK-ANALYTICSGuest (ограниченная карточка объекта недвижимости/проекта)Метрики доходности скрываются: payback, reliability, growthPercent, rentalIncomePerYear, ratingnull; ссылки на детальную аналитику не отдаются.
MASK-COMMISSIONвсе, кроме акторов с permission catalog.commissions.viewagentCommissionnull (сериализация поля пропускается). Источник данных — PropertyObjectAgentCommission (../domain/catalog-property-object-card.md). Feature flag catalog.commissions.enabled=falsenull для всех.

Маскирование применяется при формировании ответа (шаг результата), не в хранении и не в запросе к БД: одна и та же проекция обслуживает все классы акторов.

Keyset-пагинация ленты

Лента отдаёт по 20–30 карточек с бесконечным скроллом; классическая offset-пагинация не используется (дрейф страниц при пересчёте проекций).

ПараметрТипПравило
limitint1..30, default 24
cursorstring?base64url(JSON FeedCursorModel); отсутствует — первая страница
nextCursorstring? (ответ)курсор следующей страницы; null — лента исчерпана

FeedCursorModel (typed, ContractVersion = 1)

ПолеТипОбязательностьОписание
ContractVersionintДа= 1; другое значение → CATALOG_CURSOR_INVALID.
SortFeedSortДаСортировка, под которую построен курсор; несовпадение с query → CATALOG_CURSOR_INVALID.
DescendingboolДаНаправление; несовпадение → CATALOG_CURSOR_INVALID.
LastSearchRankdecimal?Нетts_rank_cd последней карточки (только поиск + Recommended).
LastPromotionBoostint?НетКлюч 1 Recommended.
LastDeveloperPriorityint?НетКлюч 2 Recommended.
LastPopularityScoredecimal?НетКлюч 3 Recommended / ключ Popularity.
LastPricedecimal?НетКлюч Price (для проектов — min_price).
LastPaybackYearsdecimal?НетКлюч Payback (нормализация в годы — ../domain/catalog-property-object-card.md, PropertyObjectPayback).
LastPublishedAtDateTimeOffset?НетКлюч PublishedAt / финальный ключ Recommended.
LastIdGuidДаTie-break: id последней карточки страницы.

Курсор не подписывается: он не содержит секретов, а подмена значений даёт лишь другую страницу публичной выдачи. Обязательные для выбранной сортировки Last*-поля отсутствуют → CATALOG_CURSOR_INVALID.

Пустая лента и промежуточные состояния при скролле

  • Пустая выдача (фильтры сузили до нуля или в каталоге нет подходящих карточек) — 200 с пустым массивом и nextCursor = null (не ошибка). Первая же страница с null-курсором — конец ленты.
  • Дрейф между страницами: витрина пересчитывается событиями источников и мутациями оверрайдов/ ранжирования во время бесконечного скролла. Keyset (в отличие от offset) устойчив к вставке/ удалению строк, но карточка, чьи ключи сортировки изменились между страницами (напр. PromotionBoost/ DeveloperPriority пересчитаны), может повторно попасть в следующую страницу или быть пропущена. Это допустимо для публичной ленты; клиент дедуплицирует карточки по id при склейке страниц. Гарантий «ровно один раз на карточку за сессию скролла» лента не даёт.
  • Свежесть относительно источника: карточка отражает последнее применённое событие chessboard/ invest-analytics/rewards/promotion (окно свежести — секунды–минуты, лаг-алерты — README сервиса, Runtime). В промежутке пользователь может видеть устаревшие цену/статус/метрику. Это только отображение: критичные для брони проверки статуса объекта выполняет crm в источнике на шаге бронирования, поэтому устаревший propertyObjectStatus в ленте не приводит к переспродаже.

Сортировки (FeedSort, enum-string)

ЗначениеDefault направлениеКлючи ORDER BY (после ключа — Id тем же направлением)Индекс
Recommended (default)только desc (фиксировано)[при searchTerm: ts_rank_cd(NameVector, query) DESC] → PromotionBoost DESCDeveloperPriority DESCPopularityScore DESCPublishedAt DESCix_*_feed (формула — ../domain/ranking-rule.md)
PriceascPrice (объекты недвижимости) / MinPrice NULLS LAST (проекты)ix_*_price
PublishedAtdescPublishedAtix_*_published_at
PopularitydescPopularityScoreix_*_popularity
Paybackascpayback_years (вычисление в query) NULLS LASTпо status + вычисление

Ранжирование застройщика и пакеты продвижения действуют только в Recommended (принятое решение — ../domain/ranking-rule.md); явные сортировки честные. Параметр desc: bool? инвертирует направление (кроме Recommended).

Сохранение фильтров и трекинг (кто чем владеет)

  • URL — источник состояния фильтров: все фильтры/сортировка/поиск передаются query-параметрами GET; ответы ленты возвращают echo применённых фильтров (appliedFilters) для синхронизации URL фронтом. Каталог состояние фильтров не хранит.
  • «Фильтры сессии» и история поиска — фронт шлёт typed ActionEvent (SearchPerformed/FilterApplied) в user-activity (../../user-activity/api/public/track-action.md); каталог эти события не пишет (ADR-0054).
  • Просмотры карточек — фронт шлёт события просмотра в user-activity; популярность возвращается в каталог консюмером pin.user-activity.property-object-view-ended.v1 (ADR-0056 п.3).

Общие response-модели (методы ссылаются)

NullableRange<decimal> (диапазон цены)

Диапазоны *Min/*Max не разносятся парой полей, а моделируются одним типом NullableRange<T>. Ценовой диапазон витрины — NullableRange<decimal>; сериализуется как { min, max }.

ЧленТипОбязательностьОписание
mindecimal?НетCatalogProjectCard.MinPrice; null — нет доступных объектов недвижимости; для Guest — округление MASK-PRICE.
maxdecimal?НетCatalogProjectCard.MaxPrice; аналогично.

PaybackModel (проекция VO PropertyObjectPayback)

ПолеТипОбязательностьОписание
valuedecimalДаPropertyObjectPayback.Value.
periodstring (enum PaybackPeriod: Years/Months)ДаPropertyObjectPayback.Period.

ReliabilityModel (проекция VO PropertyObjectReliability)

ПолеТипОбязательностьОписание
valueshortДаPropertyObjectReliability.Value.
maxshortДаPropertyObjectReliability.Max (значение — 10).

AgentCommissionModel (проекция VO PropertyObjectAgentCommission; MASK-COMMISSION)

ПолеТипОбязательностьОписание
percentMindecimalДаPropertyObjectAgentCommission.PercentMin.
percentMaxdecimalДаPropertyObjectAgentCommission.PercentMax; равен percentMin, если правило одно.
baseKindstring (enum CommissionBaseKind: BasePrice/FinalPrice)ДаБаза расчёта (дом — rewards/domain/reward-rule.md).

RewardRuleId/RuleVersion — внутренние ключи, в ответы не сериализуются.

GeoLocationModel (проекция VO ProjectGeoLocation)

ПолеТипОбязательностьОписание
latitudedecimalДа-90..90.
longitudedecimalДа-180..180.

DeveloperRefModel (застройщик — ref-модель)

Пара id + name не разносится плоскими полями, а сворачивается во вложенную ref-модель (поля-источники — дом ../domain/catalog-property-object-card.md / ../domain/catalog-project-card.md).

ПолеТипОбязательностьОписание
developerIdGuidДаDeveloperId.
developerNamestringДаDeveloperName (денормализовано из chessboard).
developerLogoFileIdGuid?НетDeveloperLogoFileId (пресайн — storage/CDN); присутствует в моделях проекта.

BuildingRefModel (корпус — ref-модель)

ПолеТипОбязательностьОписание
buildingIdGuidДаBuildingId.
buildingNamestringДаBuildingName.

Наблюдаемость и совместимость (общие правила area)

  • Logs: класс актора, применённые фильтры/сортировка, limit, размер выдачи, флаг маскирования — без значений searchTerm целиком (обрезка до 64 симв. — untrusted input) и без PII (PII в витрине нет). Metrics: catalog_feed_requests_total{method,actor_class,result}, latency histogram per route, catalog_feed_empty_results_total, catalog_commission_masked_total. Traces: span на handler, correlation — сквозной trace через traceparent. Dashboard catalog-overview; runbook — сверка/rebuild проекций (../README.md, Runtime).
  • Совместимость: новые optional-поля моделей — additive; переименование/удаление/смена типа поля, изменение правил MASK-* в сторону раскрытия, изменение схемы FeedCursorModel — breaking (ContractVersion курсора + migration note). Неизвестные значения enum в query → ValidationError; старые курсоры после breaking-смены → CATALOG_CURSOR_INVALID (фронт перезапрашивает первую страницу).
  • Rate limiting: public-методы — общий лимит gateway (значение — конфигурация платформы), защита от скрейпинга выдачи; каталог дополнительных лимитов не вводит (simplicity-first).

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