POST /api/v1/developers/projects/{projectId}/unpublish — Снятие проекта с публикации
Назначение
Менеджер застройщика снимает проект с витрины: на доработку (Published → Draft) либо временно скрывает
(Published → Hidden) — целевой статус задаётся параметром TargetStatus. Проект исчезает из публичной
ленты; объекты недвижимости и здания сохраняются. Дом полей и жизненный цикл — сущность
Проект.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | chessboard |
| API area | developers |
| Feature group | projects |
| Статус | draft |
| Документ | docs/06-services/chessboard/api/developers/unpublish-project.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (менеджер застройщика) |
| Auth policy / Permissions | chessboard.projects.manage |
| Scope (RBAC) | организация / группа проектов / проект |
| Tenant isolation | TenantId = CompanyId из JWT; чужой проект → 404 |
| Audit | обновление: UpdatedByUserId, инкремент Version |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/developers/projects/{projectId}/unpublish |
| Success status | 200 |
| Idempotency header | не применяется — повтор с тем же TargetStatus идемпотентен (no-op) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | — |
TenantId | identity claims (tenant_id) | Guid | Да | Required | Tenant isolation |
ProjectId | route | Guid | Да | существующий проект tenant-а | 404 при чужом |
TargetStatus | body | ProjectStatus | Да | только Draft (Unpublish) или Hidden (Hide); прочие значения → ValidationError | — |
Version | body | long | Да | >= 1; optimistic-сверка | — |
Модель ответа UnpublishDeveloperProjectResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | ProjectEntity.Id | — | Id проекта. |
Status | ProjectStatus | Да | Status | — | Draft или Hidden. |
Version | long | Да | Version | — | Новая версия. |
UpdatedAt | DateTimeOffset | Да | UpdatedAt | — | Момент перехода. |
Предусловия и постусловия
Предусловия:
- Актор авторизован и имеет
chessboard.projects.manageв scope, покрывающем проект. - Проект существует в tenant актора и находится в статусе
Published— единственном, из которого допустим переходDraft(Unpublish) илиHidden(Hide). - Клиент располагает актуальным
Version(получен из карточки/списка проектов).
Постусловия при успехе:
Project.Status = TargetStatus(DraftлибоHidden),Versionувеличен на 1, проставленыUpdatedByUserIdиUpdatedAt.- Здания и объекты недвижимости проекта не изменены — их собственные статусы и поля сохранены; скрытие с витрины работает только на уровне публичных проекций (каскад запросов), а не мутацией дочерних сущностей.
- В том же согласованном изменении зафиксирован факт перехода и поставлено на публикацию интеграционное
событие
pin.chessboard.project-upserted.v1со снапшотом новогоStatus. - Публичные read-операции самого
chessboardнемедленно отдают404(читают source of truth поStatus = Published); распространение вcatalog, поиск,favorites/selections— асинхронное (см. «Интеграционная последовательность»).
Постусловие при отказе: состояние проекта не изменилось, событие не публикуется, Version сохранён.
Идемпотентность
Если проект уже находится в TargetStatus — операция идемпотентна: 200 с текущим состоянием, Version
не меняется, повторное событие не публикуется.
Интеграционная последовательность
- Синхронно клиенту возвращается только результат перехода (
200+ новыйStatus/Version); никаких синхронных вызовов соседних сервисов операция не делает. - Асинхронно событие
pin.chessboard.project-upserted.v1(партиционный ключProjectId, порядок событий по проекту гарантирован) потребляют:catalog— убирает проект из ленты/шахматки; поиск — переводитProjectSearchDocumentв tombstone (ADR-0059);favorites/selections— через каскад catalog-проекций помечают недоступность. Потребители применяют снапшоты монотонно поVersion(снапшот с меньшимVersionигнорируется), поэтому переупорядочивание/дубли доставки безопасны. - Окно eventual-consistency: между ответом и обработкой события пользователь может ещё видеть проект в
ленте/поиске
catalog; самchessboardуже скрыл его. Окно ограничено лагом consumer-ов (цель — секунды, SLO README). Компенсаций/саги не требуется: событие несёт полный снапшот, повторная публикация того же состояния идемпотентна на стороне потребителя.
Алгоритм
- Контекст и доступ. Операцию выполняет менеджер застройщика; tenant — его компания; требуется право
chessboard.projects.manage. Если статус уже равенTargetStatus— идемпотентный no-op (без повторной публикации события). - Проверка входа и предусловий.
TargetStatus ∈ {Draft, Hidden},Version >= 1— иначеValidationError;- проект существует в пределах tenant-а — иначе
CHESSBOARD_PROJECT_NOT_FOUND(404); - переход допустим:
Published → Draft(Unpublish) илиPublished → Hidden(Hide); текущий статус ∉ {Published,TargetStatus} →CHESSBOARD_PROJECT_INVALID_STATUS_TRANSITION(409); Versionсовпадает с текущей — иначеCHESSBOARD_CONCURRENCY_CONFLICT(409).
- Переход статуса. Проект переводится
Published → DraftлибоPublished → HiddenпоTargetStatus. Меняется толькоStatus; объекты недвижимости и здания не мутируются (скрытие публичных проекций — каскадно в query-слое). ПроставляютсяUpdatedByUserId, инкрементVersion. - Факты и события. Публикуется событие
pin.chessboard.project-upserted.v1— снапшот с новымStatus. - Результат. Возвращаются
Id,Status,Version,UpdatedAt; HTTP200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | ProjectUpsertedKafkaEvent (новый Status) → pin.chessboard.project-upserted.v1; catalog убирает проект из ленты/шахматки, favorites/selections помечают недоступность через каскад catalog-проекций |
| Проекции / поиск | проект удаляется из Elasticsearch-проекции ProjectSearchDocument (tombstone при Status != Published, ../../search/projects.md, ADR-0059) и выпадает из публичной поисковой выдачи |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | TargetStatus ∉ {Draft, Hidden} | Список ошибок полей | no retry |
CHESSBOARD_PROJECT_NOT_FOUND | 404 | Нет проекта / чужой tenant | «Проект не найден» | no retry |
CHESSBOARD_PROJECT_INVALID_STATUS_TRANSITION | 409 | Текущий статус не Published | «Недопустимый переход статуса» | no retry |
CHESSBOARD_CONCURRENCY_CONFLICT | 409 | Version не совпал | «Данные изменены параллельно» | retry после перечитывания |
FORBIDDEN | 403 | Нет permission | «Недостаточно прав» | no retry |
Совместимость и наблюдаемость
- Контракт стабилен;
Archive— отдельная будущая операция (INV-PRJ-8: запрет при активных холдах). - Logs:
TenantId,ProjectId,Published → {TargetStatus}; metrics:chessboard_project_status_transition_total{from,to}.
Acceptance Criteria
- AC1 (Unpublish на доработку). Дано: published-проект и актуальный
Version. Когда: менеджер вызывает операцию сTargetStatus = Draft. Тогда:Status = Draft,Version+1, публикуетсяpin.chessboard.project-upserted.v1соStatus = Draft, публичная карточка проекта отдаёт404. - AC2 (Hide — временное скрытие). Дано: published-проект. Когда:
TargetStatus = Hidden. Тогда:Status = Hidden, событие несётStatus = Hidden; здания и объекты недвижимости не тронуты. - AC3 (идемпотентный повтор). Дано: проект уже в
TargetStatus. Когда: та же операция повторяется. Тогда:200no-op,Versionне меняется, повторное событие не публикуется. - AC4 (недопустимый переход). Дано: проект в
Draft. Когда: попыткаHide/Unpublish(текущий статус неPublishedи не равенTargetStatus). Тогда:CHESSBOARD_PROJECT_INVALID_STATUS_TRANSITION(409), состояние без изменений. - AC5 (конкурентная правка). Дано: клиентский
Versionустарел. Когда: операция. Тогда:CHESSBOARD_CONCURRENCY_CONFLICT(409); проигравший перечитывает проект и повторяет. - AC6 (чужой tenant). Дано: проект другого застройщика. Когда: операция. Тогда:
CHESSBOARD_PROJECT_NOT_FOUND(404), событие не публикуется. - AC7 (невалидный целевой статус). Дано:
TargetStatus = Archived(или иное вне{Draft, Hidden}). Когда: операция. Тогда:ValidationError(400).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): Chessboard API — реестр операций областей Developers и Admin, POST /api/v1/developers/projects/{projectId}/publish — Публикация проекта, PUT /api/v1/developers/projects/{projectId} — Обновление проекта
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: ../domain/project.md (жизненный цикл, INV-PRJ-8).
- Событие: ../events/project-upserted.md (снапшот, партиционный ключ, версионирование).
- Обратная операция: publish-project.md; реестр: README.md.