Entity / Aggregate: PostCategory
Дом полей SEED-справочника категорий постов новостной ленты (контекст news). Категории различаются по
типам публикующих пользователей: у разных типов пользователей доступны разные категории для публикации.
Проекции (модель ответа api/public/categories, форма публикации в post.md) ссылаются сюда и
перечисляют только дельту.
Назначение
PostCategory — платформенный справочник (seed-словарь) категорий, в которые пользователи публикуют
посты общей ленты. Ключевое свойство — AllowedAuthorTypes: список типов авторов, которым разрешено
публиковать в категорию. Например «ход строительства» доступен только менеджеру застройщика, «истории
инвесторов» — только инвестору, «новости платформы» — всем. Категория — стабильный публичный ключ ленты:
читается публично (кэш на фронте), правится редко и только администратором платформы (ADR-0053).
Справочник наполняется миграцией (seed) и далее ведётся через admin API. Записей — десятки; изменения редки, поэтому событий и денормализаций справочник не порождает.
Классификация
| Поле | Значение |
|---|---|
| Контекст | news |
| Kind | Entity (reference / seeded) — самостоятельного агрегата с событиями не образует |
| Source of truth | этот контекст (news) |
| Таблица | news.post_categories |
| Tenant isolation | не tenanted — платформенный справочник; доступность категорий конкретной компании решается на уровне workspace-настройки, а не строкой словаря (ADR-0052 п.3) |
| Concurrency | не критична: единственный писатель — администратор платформы, редкие правки |
| Удаление | не поддерживается — вместо него Archived (INV-CAT-003) |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PK | app | Идентификатор категории. |
Slug | string | Да | varchar(64), unique ux_post_categories_slug, pattern ^[a-z0-9]+(-[a-z0-9]+)*$, 1..64 | seed/admin | Стабильный kebab-ключ (construction-progress, promotions, market-analytics, agent-blog, investor-story). Не меняется — публичная ссылка/фильтр ленты (INV-CAT-001). |
Title | string | Да | varchar(128), min 1 после trim | admin | Отображаемое название категории. |
Description | string? | Нет | varchar(512) | admin | Пояснение назначения категории (маркетинговый текст). |
AllowedAuthorTypes | CategoryAuthorTypes (owned, jsonb) | Да | колонка allowed_author_types (jsonb); 1..5 уникальных элементов | seed/admin | Типы авторов, которым разрешена публикация в категорию. Домен — VO ниже. |
SortOrder | int | Да | default 0 | admin | Порядок вывода в списке категорий (по возрастанию). |
Status | CategoryStatus | Да | enum (хранится как text), varchar(16), index ix_post_categories_status (status, sort_order) | app | Active — доступна к публикации/показу; Archived — снята. |
CreatedAt | DateTimeOffset | Да | — | app | Дата создания. |
UpdatedAt | DateTimeOffset | Да | — | app | Последняя мутация. |
Enum CategoryStatus
| Значение | Описание |
|---|---|
Active | Категория доступна: показывается в списке и допускает публикацию (при прохождении eligibility). |
Archived | Снята с использования: новые публикации запрещены, существующие посты сохраняют CategoryId; из публичного списка для формы публикации исключается. Обратимо (Archived → Active). |
Enum PostAuthorType (дом — контекст news)
Типы публикующих пользователей. Дом концепта — здесь; post.md (Post.AuthorType) и eligibility-фильтр
публичного API ссылаются сюда.
| Значение | Роль (см. permission.md) | Описание |
|---|---|---|
DeveloperManager | developer | Менеджер застройщика (ход строительства, акции проекта). |
AgencyManager | agency-owner | Руководитель/менеджер агентства. |
Agent | agent | Агент (блог, личные посты). |
Investor | investor | Инвестор (истории, личные посты). |
PinEmployee | pin-manager | Менеджер ПИН (новости платформы, аналитика). |
Value objects
CategoryAuthorTypes (owned; колонка jsonb allowed_author_types)
Сериализуется в jsonb-массив enum-строк.
| Поле | Тип | Обязательность | Ограничения | Колонка |
|---|---|---|---|---|
Types | IReadOnlyList<PostAuthorType> | Да | 1..5, элементы уникальны (≥1); сериализация — enum-строки | allowed_author_types |
Инвариант VO: список непуст и без дубликатов; при нарушении — ошибка NEWS_CATEGORY_AUTHOR_TYPES_INVALID.
Порядок элементов незначим (семантика — множество).
Модели JSONB
Versioned-документ не используется: allowed_author_types — типизированный VO (закрытый enum-массив), а не
эволюционирующий документ, поэтому ContractVersion не заводится. Сериализованная форма (типизированный
массив enum-строк):
["DeveloperManager", "AgencyManager", "Agent"]
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|---|---|---|---|
PostCategory → Post | reference | posts.category_id → post_categories.id (FK, restrict) | загрузка по CategoryId при публикации (eligibility, INV-CAT-002); список категорий | restrict — категорию с постами удалить нельзя, только Archived (INV-CAT-003) |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|---|---|---|
| INV-CAT-001 SlugUnique | Slug уникален по таблице; неизменяем после создания | unique index ux_post_categories_slug + проверка перед вставкой | NEWS_CATEGORY_SLUG_DUPLICATE |
| INV-CAT-002 EligibilityGate | Публикация допустима только если actor.UserType ∈ AllowedAuthorTypes.Types (питает INV-NWS-002 в post.md) | при публикации поста: проверка типа актора против категории | NEWS_CATEGORY_AUTHOR_NOT_ELIGIBLE |
| INV-CAT-003 NoDeleteInUse | При наличии постов с CategoryId физическое удаление запрещено — только Active → Archived | при удалении/архивации: наличие постов с CategoryId (FK restrict страхует на уровне БД) | NEWS_CATEGORY_IN_USE |
| INV-CAT-004 AdminOnly | CRUD категорий доступен только m2m admin-актору (news.categories.manage) | авторизация запроса (ADR-0053) | FORBIDDEN |
Доменные и интеграционные события
Событий нет. PostCategory — reference-справочник: изменения редки, вносятся администратором платформы и
читаются публично (кэшируются на фронте). Ни доменных, ни интеграционных событий не поднимается —
потребители читают категории синхронно через публичное API каталога категорий.
Согласованность и eligibility
- Кэш публичного списка.
GET /news/categoriesкэшируется на фронте; послеArchive/Reactivateформа публикации и фильтр ленты догоняют новое состояние в пределах TTL кэша. Это допустимо: справочник меняется редко, авторитет — БД. Само правило eligibility при публикации проверяется по актуальной строке категории (не по кэшу), поэтому «протухший» список у клиента не даёт опубликовать в недопустимую категорию. - Eligibility-гейт — источник для поста. INV-CAT-002 (
actor.UserType ∈ AllowedAuthorTypes.Types) питает INV-NWS-002 в post.md: категория — вход правила, финальное решение о публикации принимает агрегатPost. ИзменениеAllowedAuthorTypesне трогает уже опубликованные посты (они сохраняютCategoryId), но меняет допустимость новых публикаций. - Archived не рвёт существующие посты. Перевод в
Archivedисключает категорию из формы публикации, но посты с этимCategoryIdостаются валидными и видимыми; приReactivateкатегория снова доступна для публикаций.
Acceptance Criteria
- Уникальный slug. Дано категория со
Slug = X; когда админ создаёт другую с тем жеSlug; тогдаNEWS_CATEGORY_SLUG_DUPLICATE(INV-CAT-001);Slugпосле создания не меняется. - Eligibility разрешает. Дано категория с
AllowedAuthorTypes = {DeveloperManager}; когда менеджер застройщика публикует в неё; тогда eligibility пройден (питает INV-NWS-002). - Eligibility запрещает. Дано та же категория; когда инвестор публикует в неё; тогда
NEWS_CATEGORY_AUTHOR_NOT_ELIGIBLE(INV-CAT-002). - Архивация с постами. Дано категория, на которую ссылаются посты; когда админ пытается её удалить;
тогда физическое удаление запрещено (FK restrict, INV-CAT-003) — доступен только
Active → Archived. - Удаление пустой. Дано категория без единого поста; когда админ удаляет; тогда удаление допустимо (INV-CAT-003).
- Реактивация. Дано
Archivedкатегория; когда админ реактивирует; тогдаActive, снова доступна для публикации и в форме. - Валидность типов авторов. Дано пустой или дублирующий список
AllowedAuthorTypes; когда сохранение; тогдаNEWS_CATEGORY_AUTHOR_TYPES_INVALID(VO:1..5уникальных). - Только админ. Дано актор без
news.categories.manage; когда попытка CRUD категории; тогдаFORBIDDEN(INV-CAT-004, ADR-0053).
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_post_categories (id); ux_post_categories_slug unique btree (slug); ix_post_categories_status btree (status, sort_order) — выборка активных категорий в порядке показа |
| Партиционирование / retention | none — десятки строк; хранится бессрочно (архивация вместо удаления) |
| Concurrency | не критична: единственный писатель — администратор платформы, редкие правки |
| Permissions / PII | чтение — public (GET /news/categories; для формы публикации — eligibility-фильтр AllowedAuthorTypes под тип актора; для чтения ленты — полный список Active). Запись — m2m admin (news.categories.manage, ADR-0053). PII нет |
| Audit | изменения журналируются admin-API (ADR-0053); отдельной audit-сущности и soft-delete нет — история статуса выражена Archived |
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Entity / Aggregate: Post, Entity / Aggregate: PostReaction + проекция PostRankingSignals (Design)
- API (8): Admin CRUD категорий постов — POST/PUT/GET /api/v1/admin/categories (m2m), GET /api/v1/authoring/posts — Мои посты и черновики, GET /api/v1/categories — Справочник категорий постов + eligibility актора, GET /api/v1/posts — Глобальная лента + поиск, GET /api/v1/posts/{id}/versions/{versionNumber} — Конкретный snapshot версии поста, GET /api/v1/posts/{idOrSuffix} — Чтение опубликованного поста, POST /api/v1/posts — Создать черновик поста, PUT /api/v1/posts/{id} — обновить метаданные поста
- Use Cases (2): UC-NWS-001. Создать черновик и опубликовать пост, UC-NWS-007. Категории по типам авторов (eligibility публикации)
- События (1): Event Contract: pin.news.post-published (PostPublishedKafkaEvent)
- registries (2): Реестр инвариантов PIN, Реестр сущностей PIN
Связанные документы
- post.md —
Post.CategoryId, eligibility-гейт публикации (INV-NWS-002 ссылается на INV-CAT-002), домPostAuthorType— здесь. ../../identity/domain/permission.md—news.categories.manage(рольadmin).../api/admin/manage-categories.md— CRUD категорий (admin API);../api/public/categories/list-categories.md— публичный список с eligibility-фильтром. UC-NWS-007.- ADR-0053 (admin API вне Identity, m2m-авторизация).