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

GET /api/v1/projects/{id} — Карточка проекта: ограниченная / полная

Назначение

Витринная карточка проекта для страниц /hotels/[id]/info и перехода из ленты. Ограниченная версия (гость, без гейта): основная информация без точных цен, статусов объектов и шахматки. Полная версия (User/RefGuest): точный диапазон цен, количество доступных объектов, метрики, переходы к ассортименту (get-project-property-objects.md) и шахматке. Каталог отдаёт витринную часть одной карточкой catalog-project-card. Просмотр карточки фиксирует фронт событием в user-activity (для гостя — с anonymous_id).

Границы метода

Контент вкладок (Информация/Локация/Финансирование/Юридическая информация/Ход строительства/Видео/Карта — декларации, банки, медиа) и сама шахматка живут в контексте chessboard (дом — chessboard/domain/project.md) — не этот метод: фронт запрашивает их отдельно по id.

Metadata

ПолеЗначение
Сервис/контекстcatalog
API areapublic
Feature groupcards
Статусdraft

Актор и доступ

ПроверкаЗначение
Actor typeGuest (ограниченная, без гейта) / RefGuest, User (полная)
Auth policy / Permissionsnone (маскирование вместо отказа)
Scopeплатформа
Tenant isolationне применяется (платформенная витрина)
Auditnone в каталоге; просмотр (в т.ч. факт просмотра полной карточки) пишет фронт → user-activity

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/projects/{id}
Success status200
Idempotencyне применяется (safe GET)
CorrelationOpenTelemetry trace (traceparent)

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
IdrouteGuidДавалидный Guid
ActorClassidentity middlewareGuest/RefGuest/UserДаневалидный токен → 401; Guest допустим (ограниченный режим)README.md

Модель ответа ProjectCardModel

Состав = ProjectListItemModel (get-projects.mdдом модели, здесь только дельта) + поля карточки ниже. Маскирование в ограниченном режиме (Guest): MASK-PRICE (priceRange округлён), MASK-STATUS (propertyObjectsAvailable = null) — как в ленте.

Поле (дельта к ProjectListItemModel)ТипОбязательностьИсточникMaskingОписание
assortmentAvailableboolДаcomputedtrue в полном режиме — фронт показывает список объектов/ассортимент (get-project-property-objects.md) и шахматку (chessboard); в ограниченном — false (шахматка гостю скрыта).
isLimitedboolДаcomputedtrue — ограниченный режим; фронт показывает предложение войти.
publishedAtstring (ISO 8601)?НетPublishedAtДата появления в каталоге («новинка» на фронте).

Ссылки для переходов (девелопер, декларации) строит фронт из developerId и id проекта; контент — chessboard, каталог URL-ы не генерирует.

Алгоритм

  1. Контекст и доступ. Актор определяется по токену (User; невалидный → 401), затем по реф-cookie referralUrl (RefGuest), иначе Guest — без отказа (ограниченный режим).
  2. Проверка входа. Id != Guid.Empty — иначе ValidationError (400). Карточка проекта должна существовать и быть опубликованной (Status = Published); если карточки нет либо она в статусе Hidden/DelistedCATALOG_PROJECT_NOT_FOUND (404) — скрытое неотличимо от несуществующего.
  3. Что читаем. Единственная карточка проекта по id (owned VO GeoLocation, Payback — часть той же строки). Объекты недвижимости проекта, декларации, шахматка не читаются (ассортимент — отдельный метод; контент — chessboard).
  4. Режим и маскирование. isLimited = (ActorClass == Guest); assortmentAvailable = !isLimited. Маппинг базовых полей — таблица ProjectListItemModel (get-projects.md); в ограниченном режиме — MASK-PRICE/MASK-STATUS.
  5. Результат. ProjectCardModel → 200 (оба режима). Консистентность — eventual (агрегаты цен/количеств пересчитываются по факту изменения объекта недвижимости, INV-2 project-card).

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Невалидный/просроченный токенбез тела схемыre-login
ValidationError400Id = Guid.EmptyСписок ошибок полейno retry
CATALOG_PROJECT_NOT_FOUND404Карточки нет либо Status ∈ {Hidden, Delisted}«Проект не найден»no retry

Acceptance Criteria

  • Полная карточка. Дано User/RefGuest и опубликованный проект. Когда запрашивает карточку. Тогда 200 с точным priceRange, заполненным propertyObjectsAvailable, assortmentAvailable = true, isLimited = false.
  • Ограниченная карточка (гость). Дано Guest. Когда запрашивает ту же карточку. Тогда 200, но priceRange округлён (MASK-PRICE), propertyObjectsAvailable = null (MASK-STATUS), assortmentAvailable = false (шахматка скрыта), isLimited = true.
  • Скрытый/несуществующий проект. Дано проект в Hidden/Delisted либо отсутствует. Когда запрашивают. Тогда CATALOG_PROJECT_NOT_FOUND (404) — скрытое неотличимо от несуществующего.
  • Невалидный токен vs гость. Дано просроченный JWT. Когда приходит запрос. Тогда 401; при полном отсутствии токена (гость) — ограниченный режим 200, не 401.
  • Свежесть агрегатов. Дано только что изменённый объект недвижимости проекта. Когда карточка читается. Тогда агрегаты цен/количеств могут отставать (eventual, INV-2 project-card) — расхождение сходится по мере пересчёта консюмерами.

Совместимость и наблюдаемость

  • Модель — надмножество ProjectListItemModel (совместная эволюция, дом — get-projects.md); новые optional-поля — additive; раскрытие маскированного гостю — breaking.
  • Logs: id, класс актора, режим; metrics — catalog_project_card_requests_total{actor_class,result}, latency histogram (SLO p99 < 200 мс — ../README.md); traces/dashboard — общие правила area (README.md).

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

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

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