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

UC-NWS-007. Категории по типам авторов (eligibility публикации)

Назначение

Автор своего типа (инвестор, агент, менеджер застройщика/агентства, сотрудник PIN) видит и публикует пост только в разрешённые его типу категории ленты новостей — доступные категории отличаются по типам публикующих. Eligibility проверяется дважды: на выдаче справочника (CanPublish) для фильтра формы и повторно на бэке при create/publish (INV-NWS-002). Дом полей — ../domain/post-category.md (AllowedAuthorTypes, PostAuthorType, INV-CAT-002) и ../domain/post.md (AuthorType, AuthorCompanyId, ProjectRefs, INV-NWS-002/003); операции — в api-документах. Связан с контекстом news.

Metadata

ПолеЗначение
IDUC-NWS-007
Контекстnews (см. docs/06-services/news/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольАвторы разных типов (PostAuthorType): Investor, Agent, AgencyManager, DeveloperManager, PinEmployee; гость — только чтение справочника (CanPublish = false)
Permissions / scopeПубликация: news.posts.create-personal (self — investor/agent/agency-owner/developer), news.posts.create-company / news.posts.create-project (застройщик/агентство в scope компании); правка своих — news.posts.manage-own. Каталог — ../../identity/domain/permission.md. Eligibility публикации = actor.UserType ∈ Category.AllowedAuthorTypes ∧ соответствующий news.posts.create-* (INV-NWS-002)
TenantTenantId = AuthorCompanyId для DeveloperManager/AgencyManager; null для Investor/Agent/PinEmployee (ADR-0052 п.4). Справочник категорий — не tenanted
Record-levelМутация — только AuthorUserId, либо менеджер компании в scope AuthorCompanyId, либо news.posts.moderate (INV-NWS-007)

Предусловие

  • Справочник PostCategory наполнен SEED-миграцией (категории по типам авторов); нужные категории — в статусе Active (INV-CAT-003).
  • Автор аутентифицирован; AuthorType = snapshot UserType из контекста актора на момент создания.
  • Для DeveloperManager/AgencyManager AuthorCompanyId берётся из scope-компании актора (не из тела).

Триггер

ПолеЗначение
ИсточникUI (форма публикации) → API call (два endpoint: выдача справочника, затем create/publish)
Триггерящий элементФорма «Создать пост» → GET /api/v1/categories?onlyEligible=true; далее POST /api/v1/posts (черновик) и POST /api/v1/posts/{id}/publish

Основной Поток

Eligibility — один и тот же предикат AuthorType ∈ AllowedAuthorTypes на выдаче и на записи.

Фаза 1. Выдача доступных категорий — ../api/public/categories/list-categories.md

  1. Автор открывает форму публикации; фронт вызывает GET /api/v1/categories?onlyEligible=true.
  2. Загружаются Active-категории; для каждой вычисляется CanPublish = ActorUserType ∈ AllowedAuthorTypes (тот же предикат, что INV-CAT-002); при OnlyEligible = true остаются только CanPublish = true. Гость (Anonymous) → CanPublish = false → пустой список.
  3. Фронт показывает форме только разрешённые типу автора категории; полный Active-список (без onlyEligible) остаётся для фильтров ленты.

Фаза 2. Create/publish с повторной проверкой eligibility

  1. Автор выбирает категорию и сохраняет черновик — POST /api/v1/posts (../api/authoring/create-post-draft.md). Проверить: категория существует ∧ Active, затем eligibility INV-NWS-002 / INV-CAT-002: AuthorType ∈ category.AllowedAuthorTypes.Types — иначе NEWS_CATEGORY_NOT_ALLOWED (поток A1).
  2. Attribution по типу автора (INV-NWS-003):
    • застройщик (DeveloperManager) — публикует в construction-progress / promotions с привязкой к компании (AuthorCompanyId = TenantId) и/или проектам (ProjectRefs, <= 20); непустой ProjectRefs допустим только для DeveloperManager;
    • агент (Agent) — публикует в agent-blog / promotions к себе (AuthorCompanyId = null, ProjectRefs пуст); руководитель агентства (AgencyManager) — с AuthorCompanyId, но без ProjectRefs;
    • инвестор (Investor) — публикует в investor-story от себя (AuthorCompanyId = null);
    • нарушение (напр. ProjectRefs не-застройщиком, AuthorCompanyId у инвестора) → NEWS_ATTRIBUTION_NOT_ALLOWED (поток A2).
  3. Автор публикует — POST /api/v1/posts/{id}/publish (../api/authoring/publish-post.md). Повторно проверяется INV-NWS-002 (категория могла быть архивирована/переназначена между черновиком и публикацией), контент (INV-NWS-004), пишется иммутабельный PostVersion (INV-NWS-005) и публикуется pin.news.post-published.v1. Переход, версия и факт публикации события фиксируются в одном согласованном изменении.
  4. Результат — { Status = Published, VersionNumber }; пост попадает в ленту (partial ix_posts_feed where status='Published') и в Elasticsearch-проекцию поиска news-posts в категории, разрешённой типу автора.

Что фиксировано, что перепроверяется. AuthorType — snapshot UserType на момент создания черновика; повторная проверка на publish берёт сохранённый Post.AuthorType, а не текущий UserType актора — смена роли пользователя между черновиком и публикацией eligibility не переоткрывает и не ужесточает. Перепроверяется же текущее состояние категории: между черновиком и publish её могли архивировать (Status = ArchivedNEWS_CATEGORY_NOT_FOUND) или переназначить AllowedAuthorTypes (тип выпал из набора → NEWS_CATEGORY_NOT_ALLOWED). На выдачу справочника (CanPublish, фаза 1) влияет текущий UserType, поэтому список категорий и уже сохранённый черновик могут разойтись — publish разрешает конфликт по сохранённому AuthorType и актуальному статусу/набору категории.

SEED категорий (AC5; дом полей — ../domain/post-category.md)

AllowedAuthorTypes — enum-строки PostAuthorType (1..5, уникальны).

SlugTitleAllowedAuthorTypes
platform-newsНовости платформыInvestor, Agent, AgencyManager, DeveloperManager, PinEmployee
construction-progressХод строительстваDeveloperManager
promotionsАкцииDeveloperManager, AgencyManager, Agent
agent-blogБлог агентаAgent, AgencyManager
investor-storyИстории инвесторовInvestor
market-analyticsАналитика рынкаPinEmployee

Диаграмма

Альтернативные Потоки

A1. Попытка публикации в недоступную типу автора категорию

  1. Автор указывает CategoryId, тип которого не входит в AllowedAuthorTypes (обход UI-фильтра фазы 1 или гонка с архивацией/правкой категории администратором между выдачей и записью).
  2. Проверка INV-NWS-002 на create-draft и publish (AuthorType ∉ category.AllowedAuthorTypes.Types) → NEWS_CATEGORY_NOT_ALLOWED (403). Пост не создаётся/не публикуется; событий нет.

Завершается ошибкой; клиент перечитывает GET /api/v1/categories?onlyEligible=true и выбирает доступную.

A2. Привязка ProjectRefs/AuthorCompanyId несоответствующим типом автора

  1. Не-застройщик (Agent/Investor/AgencyManager/PinEmployee) передаёт непустой ProjectRefs, либо инвестор/агент/сотрудник передаёт AuthorCompanyId (INV-NWS-003: ProjectRefs — только DeveloperManager; AuthorCompanyId — только DeveloperManager/AgencyManager и должен совпадать со scope актора).
  2. Проверка attribution → NEWS_ATTRIBUTION_NOT_ALLOWED (403). Пост не создаётся.

Завершается ошибкой; клиент публикует без недопустимой привязки (агент/инвестор — «к себе»).

Постусловие

  • Пост создан/опубликован в категории, разрешённой типу автора (AuthorType ∈ AllowedAuthorTypes), attribution соответствует типу (застройщик — компания/проекты; агент/инвестор — к себе).
  • При публикации: Status = Published, зафиксирован PostVersion (INV-NWS-005), опубликован pin.news.post-published.v1; пост в ленте и в Elasticsearch-проекции поиска.
  • Справочник PostCategory не изменён (read-only для авторов; запись — только admin API, INV-CAT-004).
  • При A1/A2 система не изменена: ни Post, ни события.

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
VALIDATION_FAILED400Пустой CategoryId; ProjectRefs > 20; нераспознанный литерал onlyEligibleИсправить форму
UNAUTHORIZED401Нет/просрочен токен (аноним справочник читает без токена)Повторная авторизация
NEWS_CATEGORY_NOT_FOUND404Категория не найдена или Status = ArchivedПеречитать справочник категорий
NEWS_CATEGORY_NOT_ALLOWED403AuthorType ∉ Category.AllowedAuthorTypes (INV-NWS-002 / INV-CAT-002), A1Выбрать доступную категорию
NEWS_ATTRIBUTION_NOT_ALLOWED403ProjectRefs/AuthorCompanyId не соответствуют типу автора или scope (INV-NWS-003), A2Убрать недопустимую привязку
403 (record-level)403Не автор / не менеджер scope / нет news.posts.moderate (INV-NWS-007)Нет прав на пост

Доменные События

СобытиеКогда поднимаетсяЭффект
PostContentChangedEventСоздание черновика / правка Title/Subtitle/Name/блоковпересчёт SearchText в той же транзакции (INV-NWS-008); для Published-поста — upsert Elasticsearch-проекции поиска
PostVersionCapturedEventPublish/Republish (→ Published)публикация PostPublishedKafkaEvent

Справочник PostCategory доменных событий не порождает (reference-словарь, ../domain/post-category.md).

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
PostPublishedKafkaEventpin.news.post-published.v1Публикация поста в разрешённой категории (ContractVersion = 1, key = PostId)notifications (подписчики автора), user-activity (таймлайн автора, опц.)

Контракт/payload — ../events/post-published.md; публикация — атомарно с переходом и записью PostVersion. Выдача категорий и create-draft интеграционных событий не порождают.

Acceptance Criteria

#КритерийПроверка
AC1GET /api/v1/categories?onlyEligible=true возвращает только категории с CanPublish = true для ActorUserType; гость → пустой списокIntegration-тест выдачи под разными UserType + анонимом
AC2CanPublish = ActorUserType ∈ AllowedAuthorTypes (тот же предикат, что гейт публикации INV-CAT-002); полный список без onlyEligible не фильтруетсяТест соответствия CanPublish ↔ eligibility publish
AC3Публикация в недоступную типу категорию (A1) → NEWS_CATEGORY_NOT_ALLOWED (403) и на create-draft, и на publish (повторная INV-NWS-002)Негативный тест обоих endpoint
AC4Attribution по типу: застройщик — ProjectRefs/AuthorCompanyId разрешены; ProjectRefs не-застройщиком / AuthorCompanyId инвестором → NEWS_ATTRIBUTION_NOT_ALLOWED (A2)Позитивный/негативный тесты INV-NWS-003 по каждому типу
AC5SEED категорий: construction-progressDeveloperManager; agent-blogAgent/AgencyManager; investor-storyInvestor; promotions→застройщик+агент; platform-news→всеТест SEED-миграции
AC6Успешная публикация оставляет пост в разрешённой категории (Status = Published, PostVersion, опубликован pin.news.post-published.v1)Тест happy path + assert публикации события/версии
AC7Publish перепроверяет eligibility по сохранённому Post.AuthorType и текущему состоянию категории: смена роли актора между черновиком и publish на исход не влияет; архивация/переназначение AllowedAuthorTypes даёт NEWS_CATEGORY_NOT_FOUND/NEWS_CATEGORY_NOT_ALLOWEDТесты: смена UserType перед publish (не влияет); архивация/правка категории между draft и publish

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

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

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