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

POST /api/v1/posts/{id}/comments-settings — переключить комментарии

Назначение

Дать автору (или менеджеру компании в scope поста) закрыть либо открыть обсуждение под своим постом: установить Post.CommentsEnabled = enabled. При выключении существующие комментарии сохраняются и остаются видимыми (дерево не трогается), а добавление новых запрещается гейтом INV-NWS-006 / INV-CMT-001 в add-comment.md. Операция без интеграционных событий и без пересчёта поиска/счётчиков.

Metadata

ПолеЗначение
Сервис/контекстnews
API areaauthoring
Feature groupauthoring
Статусdraft
Документdocs/06-services/news/api/authoring/toggle-comments.md

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

ПроверкаЗначение
Actor typeUser (аутентифицированный автор; guest — 401)
Auth policy / PermissionsJWT identity; record-level (ниже) ИЛИ permission news.posts.manage (INV-NWS-007)
Scope (RBAC)self + company scope (менеджер компании — по AuthorCompanyId поста)
Record-levelactor.UserId == Post.AuthorUserId, либо менеджер компании в scope Post.AuthorCompanyId, либо news.posts.manage; иначе FORBIDDEN (INV-NWS-007)
Tenant isolationTenantId = Post.AuthorCompanyId (nullable; null — пост инвестора/сотрудника вне tenant, ADR-0052); record-level — первичный гейт мутации
Auditupdate: UpdatedByUserId = actor.UserId, Version++; PII нет

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/posts/{id}/comments-settings
Success status200
Idempotency headerне применяется — операция идемпотентна по природе (установка булева значения; повтор с тем же enabled даёт тот же результат)
Correlationсквозной trace через traceparent

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

Единая секция: HTTP-источник + типизированное поле request-модели SetCommentsEnabledCommand.

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
PostIdroute (id)GuidДа!= Guid.Empty; существующий неудалённый Post (шаг 2)
EnabledbodyboolДаобязателен в теле; true — открыть, false — закрыть приём новых
ActorUserIdidentity claimsGuidДаиз JWT акторастамп мутации, не из тела

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

ПолеТипОбязательностьИсточникMaskingОписание
PostIdGuidДаPost.IdИдентификатор поста.
CommentsEnabledboolДаPost.CommentsEnabled (после мутации)Актуальное состояние приёма новых комментариев.

Алгоритм

  1. Контекст и доступ. Выполняет автор поста или менеджер его компании в scope (User); гость → 401. Требуется record-level (ниже) или право news.posts.manage (INV-NWS-007).
  2. Проверка входа и доступа. PostId != Guid.Empty, Enabled присутствует в теле, иначе VALIDATION_FAILED. Найти пост по PostId; если не найден или Archived (soft-deleted) → NEWS_POST_NOT_FOUND. Флаг переключается в любом живом статусе (Draft/Published/Unpublished); Archived уже отсечён. Record-level (INV-NWS-007): актор — автор, либо менеджер компании в scope AuthorCompanyId, либо носитель news.posts.manage; иначе → FORBIDDEN.
  3. Переключение флага. Установить CommentsEnabled = Enabled (идемпотентно — повтор с тем же значением даёт тот же результат), actor-stamp. Статус поста не меняется, дерево комментариев не затрагивается. При выключении (false) — «мягкий» гейт: существующие комментарии остаются Visible и читаемы, а добавление новых блокируется на входе add-comment проверкой INV-NWS-006 (Post.CommentsEnabled) / INV-CMT-001. Событий нет: подписчиков не уведомляем, поиск и денормализованные счётчики от флага не зависят.
  4. Результат. CommentsSettingsResult { PostId, CommentsEnabled }200.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone — переключение комментариев событий не публикует
External callsnone
Cache / projections / searchnone — search-проекция и CommentsCount от флага не зависят; существующие комментарии не пересчитываются
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400PostId == Guid.Empty / отсутствует Enabled в теле«Проверьте параметры запроса»no retry
NEWS_POST_NOT_FOUND404пост не найден либо Archived (soft-delete-фильтр)«Пост не найден»no retry
FORBIDDEN403актор не автор, не менеджер компании поста и без news.posts.manage (INV-NWS-007)«Недостаточно прав»no retry

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

Предусловия (истинны до сценария):

  • актор — автор поста, менеджер компании в scope AuthorCompanyId либо носитель news.posts.manage (INV-NWS-007);
  • пост существует и в живом статусе Draft/Published/Unpublished (Archived отсечён).

Постусловия (гарантированы после переключения):

  • Post.CommentsEnabled = Enabled, actor-stamp UpdatedByUserId, доменная версия +1; статус поста и дерево комментариев не изменены;
  • при Enabled = false существующие комментарии остаются Visible и читаемы; добавление новых блокируется гейтом INV-NWS-006 / INV-CMT-001 на входе add-comment;
  • интеграционных событий, пересчёта поиска и счётчиков нет — флаг не влияет на news-posts и CommentsCount.

Операция полностью локальна: синхронных кросс-сервисных вызовов и исходящих событий нет — подписчиков не уведомляем, единственный «потребитель» флага — операция add-comment, читающая CommentsEnabled при добавлении.

Acceptance Criteria

  • Закрытие обсуждения. Дано: автор, пост Published с CommentsEnabled = true. Когда: POST /api/v1/posts/{id}/comments-settings {enabled:false}. Тогда: 200, CommentsEnabled = false, существующие комментарии видимы, дерево не тронуто.
  • Открытие обсуждения. Дано: пост с CommentsEnabled = false. Когда: POST {enabled:true}. Тогда: 200, CommentsEnabled = true.
  • Гейт новых комментариев. Дано: CommentsEnabled = false. Когда: кто-то вызывает add-comment. Тогда: добавление отклонено гейтом INV-NWS-006 / INV-CMT-001 (см. add-comment.md).
  • Идемпотентный повтор. Дано: то же значение Enabled. Когда: повтор POST. Тогда: тот же 200, результат не меняется.
  • Архивный/несуществующий пост. Дано: пост Archived/soft-deleted или отсутствует. Когда: POST. Тогда: 404 NEWS_POST_NOT_FOUND.
  • Не владелец. Дано: актор не автор/не менеджер scope и без news.posts.manage (INV-NWS-007). Когда: POST. Тогда: 403 FORBIDDEN.
  • Невалидный вход. Дано: PostId == Guid.Empty или отсутствует Enabled в теле. Когда: POST. Тогда: 400 VALIDATION_FAILED.
  • Гость. Дано: запрос без JWT. Когда: POST. Тогда: 401.

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

  • CommentsSettingsResult — стабильный контракт; новые optional поля запроса/ответа — additive, удаление/переименование/смена типа — breaking (migration + rollout). Семантика «выключено» — запрет новых комментариев без удаления существующих — часть контракта (INV-NWS-006).
  • Logs: comments-settings toggled (PostId, CommentsEnabled, ActorUserId); metrics: news_comments_settings_total{enabled}, latency p95; traces — сквозной trace через traceparent; dashboard news-overview; runbook — общий authoring-troubleshooting (record-level отказы).

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

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

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

  • Сущности: post.md (флаг CommentsEnabled, гейт INV-NWS-006, record-level INV-NWS-007), comment.md (INV-CMT-001 — новый комментарий только при CommentsEnabled = true; существующие узлы сохраняются).
  • Смежный метод: add-comment.md (потребитель гейта при добавлении); карта областей — README.
  • Permission-каталог: identity/domain/permission.md (news.posts.manage).
  • ADR: ADR-0052 (tenancy), ADR-0056 (масштаб).