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

GET /api/v1/public/projects/{projectId} — Карточка проекта (ограниченная/полная)

Назначение

Карточка проекта (ЖК/апарт-отеля) для публичного каталога: вкладки «Информация / Локация / Финансирование / Юридическая информация / Ход строительства / Видео / Карта». Один endpoint обслуживает обе версии карточки: ограниченную для неавторизованных (скрываются точные цены, счётчики статусов и доступ к шахматке) и полную для авторизованных (инвестор/агент). Зеркалируется в agents area (GET /api/v1/agents/projects/{projectId}) той же операцией с обязательной авторизацией.

Вне scope

Запись просмотра в историю — ответственность клиента/user-activity (событие просмотра), не этого метода.

Metadata

ПолеЗначение
Сервис/контекстchessboard
API areapublic (зеркало: agents)
Feature groupprojects
Статусdraft
OwnerPIN Platform Core
Документdocs/06-services/chessboard/api/public/projects/get-project.md

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

ПроверкаЗначение
Actor typeAnonymous (ограниченная проекция) / User — инвестор, агент, менеджер застройщика (полная проекция)
Auth policy / Permissionsnone — public endpoint; полнота проекции определяется признаком авторизации
Scope (RBAC)не применяется (платформенное чтение)
Record-levelне применяется
Tenant isolationКонтролируемый bypass tenant-фильтра только для среза Status = Published и Developer.Status = Published (ADR-0052)
Auditnone (read; просмотры трекает user-activity)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/public/projects/{projectId}
Success status200
Idempotency headerне применяется (idempotent by design — GET)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
ProjectIdrouteGuidДаRequired, != Guid.Empty
IsAuthenticatedidentity claimsboolДа (вычисляется)из контекста авторизации; аноним = falseопределяет masking
ActorUserIdidentity claimsGuid?Нетnull для анонимане логируется в теле ответа

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

Проекция ProjectEntity + вложенные summary-модели. Masking-колонка: правило для анонима (авторизованный получает значение всегда).

ПолеТипОбязательностьИсточникMasking (аноним)Описание
IdGuidДаProject.IdИдентификатор проекта.
NamestringДаProject.NameНазвание ЖК/отеля.
Descriptionstring?НетProject.DescriptionВкладка «Информация».
TypeProjectType (string)ДаProject.TypeТип (reference-data.md).
ClassProjectClass (string)ДаProject.ClassКласс.
ConstructionStatusConstructionStatus (string)ДаProject.ConstructionStatusСтатус строительства.
CountryIdshortДаProject.CountryIdСсылка на страну справочника (handbook-replica.md); display-имя — публичный lookup handbook.
RegionIdlongДаProject.RegionIdСсылка на регион справочника (у Published-проекта резолвлена, INV-PRJ-3).
RegionNamestringДаProject.RegionName (денормализация)Display-имя региона.
CityIdlongДаProject.CityIdСсылка на город справочника (INV-PRJ-3).
CityNamestringДаProject.CityName (денормализация)Display-имя города.
Starsshort?НетProject.StarsЗвёздность отеля.
LocationProjectLocationDocumentДаProject.Location (типизированный jsonb-документ)Вкладки «Локация»/«Карта» (модель — дом project.md).
InfrastructureProjectInfrastructureDocumentДаProject.InfrastructureИнфраструктура.
FinancingProjectFinancingCardModelДаProject.Financing + резолв банковВкладка «Финансирование» (id банков заменены на summary — модель ниже).
LegalInfoProjectLegalInfoDocumentДаProject.LegalInfo— (публично по 214-ФЗ)Вкладка «Юридическая информация», декларации.
ConstructionProgressProjectConstructionProgressDocumentДаProject.ConstructionProgressВкладки «Ход строительства»/«Видео».
PresentationVideoUrlstring?НетProject.PresentationVideoUrlГлавное видео.
CoverImageFileIdGuid?НетProject.CoverImageFileIdОбложка (пресайн — storage/CDN).
PriceNullableRange<decimal>?НетProject.Price (Price.Min/Price.Max)null (точные цены скрыты)Диапазон цен свободных объектов недвижимости.
CurrencyIdshortДаProject.CurrencyIdСсылка на валюту справочника (RUB-only, ADR-0048); display-код/знак — публичный lookup handbook.
PropertyObjectsTotalCountintДаProject.PropertyObjectsTotalCountВсего объектов недвижимости (карточка ленты).
PropertyObjectsFreeCountint?НетProject.PropertyObjectsFreeCountnull (производная статусов объектов)Свободных объектов недвижимости.
PaybackYearsdecimal?НетProject.PaybackYearsОкупаемость (детальная аналитика — invest-analytics).
DeveloperDeveloperSummaryModelДазагруженный DeveloperEntityКарточка застройщика (ниже); ссылка «переход на девелопера».
HotelierHotelierSummaryModel?Нетзагруженный HotelierEntityОператор апарт-отеля.
BuildingsIReadOnlyList<BuildingSummaryModel>Да (может быть пустым)Filter по зданиямPublished-здания проекта (ниже).
IsLimitedboolДавычисляется: !IsAuthenticatedtrue — отдана ограниченная проекция; фронт показывает CTA «войдите».
IsChessboardAvailableboolДавычисляется: IsAuthenticatedДоступ к шахматке (аноним — скрыта).
CreatedAtDateTimeOffsetДаProject.CreatedAtДата добавления.
VersionlongДаProject.VersionВерсия агрегата (client-side cache validation).

Вложенная модель DeveloperSummaryModel (проекция developer.md)

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаDeveloper.Id= CompanyId identity.
NamestringДаDeveloper.NameНазвание.
LogoFileIdGuid?НетDeveloper.LogoFileIdЛого.
FoundedYearshort?НетDeveloper.FoundedYearГод основания.
ReliabilityValuedecimal?НетDeveloper.Highlights.ReliabilityValueНадёжность (прототип reliability.value).
ReliabilityMaxdecimalДаDeveloper.Highlights.ReliabilityMaxШкала (прототип reliability.max).
ObjectsDeliveredCountint?НетDeveloper.Highlights.ObjectsDeliveredCountСдано объектов.
ObjectsInProgressCountint?НетDeveloper.Highlights.ObjectsInProgressCountСтроится.

Вложенная модель HotelierSummaryModel (проекция hotelier.md)

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаHotelier.IdИдентификатор.
NamestringДаHotelier.NameНазвание отельера.
LogoFileIdGuid?НетHotelier.LogoFileIdЛого.

Вложенная модель BuildingSummaryModel (проекция building.md)

ПолеТипОбязательностьИсточникMasking (аноним)Описание
IdGuidДаBuilding.IdИдентификатор здания.
NamestringДаBuilding.Name«Литер 3», «Корпус Б».
FloorsTotalshortДаBuilding.FloorsTotalЭтажность.
SectionsCountshort?НетBuilding.SectionsCountСекций.
DeliveryYearshort?НетBuilding.DeliveryYearГод сдачи.
DeliveryQuartershort?НетBuilding.DeliveryQuarterКвартал сдачи.
ConstructionStatusConstructionStatus (string)ДаBuilding.ConstructionStatusСтатус строительства здания.
ReadinessPercentdecimalДаBuilding.ReadinessPercentГотовность.
PropertyObjectsTotalCountintДаBuilding.PropertyObjectsTotalCountВсего объектов недвижимости.
PropertyObjectsFreeCountint?НетBuilding.PropertyObjectsFreeCountnullСвободных объектов недвижимости.

Вложенная модель ProjectFinancingCardModel

Дельта к ProjectFinancingDocument (project.md): id банков резолвятся в summary для отображения без второго запроса.

ПолеТипОбязательностьИсточникMaskingОписание
SalesModelSalesModel (string)ДаFinancing.SalesModelДДУ/ДКП/Mixed.
EscrowBanksIReadOnlyList<BankSummaryModel>Да (может быть пустым)резолв банков по Financing.EscrowBankIdsЭскроу-банки.
ProjectFinancingBankBankSummaryModel?Нетрезолв банка по Financing.ProjectFinancingBankIdБанк проектного финансирования.
MortgageAvailableboolДаFinancing.MortgageAvailableИпотека доступна.
MortgageBanksIReadOnlyList<BankSummaryModel>Да (может быть пустым)резолв банков по Financing.MortgageBankIdsИпотечные банки.
InstallmentAvailableboolДаFinancing.InstallmentAvailableРассрочка есть (программы — crm).

Вложенная модель BankSummaryModel (проекция bank.md)

ПолеТипОбязательностьИсточникMaskingОписание
IdGuidДаBank.IdИдентификатор.
NamestringДаBank.NameНазвание банка.
LogoFileIdGuid?НетBank.LogoFileIdЛого.

Предусловия и постусловия

Предусловия:

  • Вызов анонимом (ограниченная проекция) или авторизованным пользователем (полная); признак авторизации определяет masking.
  • Проект видим только по публичному срезу: Project.Status = Published и Developer.Status = Published (INV-DEV-6). Иначе — 404 (единый код без раскрытия существования Draft/Hidden).

Постусловия (read — состояние не меняется):

  • Возвращена карточка с summary застройщика, отельера, банков и published-зданий. Для анонима: Price = null, PropertyObjectsFreeCount = null (проект и каждое здание), IsLimited = true, IsChessboardAvailable = false; для авторизованного значения отдаются как есть.
  • Архивные/отсутствующие банки или отельер молча выпадают из проекций — карточка не падает.
Freshness

Price и счётчики объектов недвижимости — денормализация с лагом debounce-джоба project-aggregates-recalc; просмотры в историю пишет user-activity, не этот метод.

Алгоритм

Собрать карточку проекта с summary-моделями застройщика, отельера, банков и зданий; полнота полей зависит от авторизации.

  1. Контекст и доступ. Вызывает аноним (ограниченная проекция) или авторизованный пользователь (полная); признак авторизации определяет masking. Чтение — только по публичному срезу (Published-иерархия).
  2. Проверка входа. ProjectId задан; иначе — VALIDATION_ERROR. Существование и публичность — шаг 3 (единый 404: отсутствие и непубличность неотличимы снаружи, без утечки существования Draft/Hidden).
  3. Что читаем (родитель до дочерних):
    • Проект по id со статусом Published и не удалённый; не найден → CHESSBOARD_PROJECT_NOT_FOUND.
    • Застройщик проекта; если его статус не Published — проект публично невидим (INV-DEV-6 HiddenCascades) → CHESSBOARD_PROJECT_NOT_FOUND (тот же код — причину не раскрываем).
    • Отельер проекта (если задан); архивный отельер даёт null — карточка не падает.
    • Банки финансирования (эскроу, проектного финансирования, ипотечные) по id из Financing; отсутствующие или архивные id молча выпадают из проекций.
    • Published-здания проекта (сортировка по Name asc).
  4. Сборка ответа и masking. Поля — по таблицам моделей: плоские значения проекта, типизированные jsonb-документы, FinancingProjectFinancingCardModel (id банков заменены на summary), developerDeveloperSummaryModel (в т.ч. Highlights.Reliability*), buildings → список BuildingSummaryModel. При анониме: Price = null, PropertyObjectsFreeCount = null (проект и каждое здание), IsLimited = true, IsChessboardAvailable = false; для авторизованного значения отдаются как есть, IsLimited = false, IsChessboardAvailable = true.
  5. Результат. Карточка проекта, статус 200; не найдено/непублично → 404; нарушение проверки входа → 400. Просмотры в историю пишет user-activity, не этот метод.

Диаграмма

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_ERROR400ProjectId == Guid.Empty«Некорректный идентификатор проекта»no retry
CHESSBOARD_PROJECT_NOT_FOUND404Проект не существует / не Published / удалён / застройщик не Published«Проект не найден» (без раскрытия причины)no retry
INTERNAL_ERROR500Инфраструктурный сбой (PostgreSQL недоступен)genericretry (client backoff)

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

Breaking changes

Новые optional поля ответа — additive (без breaking); удаление/переименование/смена типа поля или правила masking в сторону раскрытия — breaking (новая версия route).

  • Enum-поля сериализуются строками; клиент обязан иметь fallback на unknown-значения (reference-data.md).
  • Logs: projectId, actor type (anonymous/user), latency; без PII. Metrics: chessboard_public_project_requests_total{authenticated}, histogram latency (SLO p99 < 300 мс — README). Traces: span chessboard.get-public-project со сквозной корреляцией.
  • Dashboard/runbook: chessboard-overview (README «Runtime И Эксплуатация»).

Acceptance Criteria

  • AC1 (полная карточка). Дано: авторизованный, published-проект published-застройщика. Когда: GET. Тогда: 200, Price/PropertyObjectsFreeCount заполнены, IsLimited = false, IsChessboardAvailable = true.
  • AC2 (masking для анонима). Дано: тот же проект, вызывает аноним. Когда: GET. Тогда: 200, Price = null, все PropertyObjectsFreeCount = null (проект и здания), IsLimited = true, IsChessboardAvailable = false.
  • AC3 (каскад скрытия застройщика). Дано: застройщик не Published (INV-DEV-6). Когда: GET. Тогда: CHESSBOARD_PROJECT_NOT_FOUND (404) — тот же код, причина не раскрывается.
  • AC4 (непубличный/несуществующий проект). Дано: проект Draft/Hidden/удалён/не существует. Когда: GET. Тогда: CHESSBOARD_PROJECT_NOT_FOUND (404).
  • AC5 (архивные ссылки не ломают карточку). Дано: отельер архивен или банк из Financing отсутствует. Когда: GET. Тогда: 200, Hotelier = null / банк выпал из списка, без ошибки.
  • AC6 (валидация). Дано: ProjectId = Guid.Empty. Когда: GET. Тогда: VALIDATION_ERROR (400).

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

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

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