GET /api/v1/categories — Справочник категорий постов + eligibility актора
Назначение
Возвращает публичный справочник Active-категорий новостной ленты (PostCategory,
post-category.md) в порядке показа, с per-actor признаком
CanPublish — «может ли текущий пользователь публиковать в категорию». Обслуживает два
сценария фронта: (а) фильтры глобальной ленты — полный список Active-категорий (аноним/пользователь);
(б) форма публикации — только доступные автору категории (onlyEligible = true). Справочник — стабильный
публичный ключ ленты, правится редко и только администратором платформы (ADR-0053), поэтому ответ кэш-дружелюбен
(HTTP-кэш фронта/CDN).
| Поле | Значение |
|---|
| Сервис/контекст | news (Pin.News.Api) |
| API area | public |
| Feature group | categories |
| Статус | draft |
| Документ | docs/06-services/news/api/public/categories/list-categories.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Anonymous (гость ленты) или User (авторизованный — JWT identity) |
| Auth policy / Permissions | none — публичное чтение справочника; permission не требуется. onlyEligible/CanPublish используют actor.UserType из JWT, если токен есть |
| Scope (RBAC) | не применяется — платформенный справочник (не tenanted, ADR-0052 п.3) |
| Record-level | не применяется |
| Tenant isolation | не применяется — PostCategory не tenanted (платформенный словарь, post-category.md); request не tenanted |
| Audit | none (публичное чтение reference data; PII нет) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/v1/categories |
| Success status | 200 (в т.ч. пустой результат — Items = [], не ошибка) |
| Idempotency header | не применяется (safe GET) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
OnlyEligible | query (onlyEligible) | bool? | Нет (default false) | Только true/false; при true — вернуть лишь категории с CanPublish = true | — |
ActorUserType | identity claims | PostAuthorType? | Нет | Заполнен для User из JWT; null для Anonymous — в body/query не принимается | из actor-context, не из запроса |
Пагинация не экспонируется: справочник — десятки строк (post-category.md),
возвращается одной страницей.
Модель ответа (список CategoryItem)
Список возвращается одной страницей: Items + Page = 1, PageSize = TotalCount = число элементов в
Items. Финансовых данных и PII в модели нет.
Модель CategoryItem
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Id | Guid | Да | PostCategory.Id | — | Идентификатор категории (стабильный ключ фильтра ленты и привязки поста). |
Slug | string | Да | PostCategory.Slug | — | Kebab-код (construction-progress, investor-story, …), INV-CAT-001. |
Title | string | Да | PostCategory.Title | — | Отображаемое название. |
Description | string? | Нет | PostCategory.Description | — | Пояснение назначения категории (может отсутствовать). |
AllowedAuthorTypes | IReadOnlyList<string> (enum PostAuthorType) | Да | PostCategory.AllowedAuthorTypes.Types (owned VO CategoryAuthorTypes) | — | Типы авторов, которым разрешена публикация (1..5, enum строкой). |
CanPublish | bool | Да | computed (шаг 4) | — | true, если ActorUserType ∈ AllowedAuthorTypes; для Anonymous всегда false. |
Алгоритм
- Контекст и доступ. Публичное чтение справочника: актор —
Anonymous (JWT опционален) или User;
permission не требуется. Тип автора ActorUserType берётся из JWT для User, для Anonymous — null.
Справочник платформенный (не tenanted).
- Проверка входа.
OnlyEligible — булев флаг (default false); нераспознанный литерал →
ValidationError (400). Иных входных ограничений нет; OnlyEligible = true для Anonymous — валидный
запрос (результат — пустой список).
- Выборка. Вернуть все категории со статусом
Active в порядке показа (SortOrder ↑, индекс
ix_post_categories_status); AllowedAuthorTypes разворачивается в enum-строки. Выборка глобальна (не
tenanted), потому кэш-дружелюбна.
- Признак публикации и фильтр. Для каждой категории вычислить
CanPublish: для User — ActorUserType
входит в AllowedAuthorTypes (тот же eligibility-предикат, что и гейт публикации INV-CAT-002); для
Anonymous — всегда false. При OnlyEligible = true — оставить только категории с CanPublish = true
(для Anonymous → пусто); порядок по SortOrder сохраняется. Справочник read-only (запись только
admin-API, INV-CAT-004); событий не порождает.
- Результат. Список
CategoryItem → 200 (пусто → Items = [], TotalCount = 0). Справочник —
source of truth; при чтении из HTTP-кэша фронта/CDN возможна задержка до TTL / до admin-инвалидации
(изменения редки, ADR-0056).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | none (read-only) |
| External calls | none |
| Cache / projections / search | HTTP-кэш ответа: Anonymous + OnlyEligible=false → Cache-Control: public, max-age + ETag (CDN/браузер); User/OnlyEligible=true → private (ответ зависит от ActorUserType). Server-side Redis не используется (ADR-0056); индекс ix_post_categories_status |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
ValidationError | 400 | Нераспознанный литерал onlyEligible | Список ошибок полей | no retry |
| — (auth) | 401 | Передан невалидный/просроченный JWT (аноним без токена — допустим) | без тела схемы (фронт очищает сессию) | re-login |
| — (пустой результат, не ошибка) | 200 | Под фильтром категорий нет (напр. onlyEligible=true у анонима) | Items = [], TotalCount = 0 | — |
Совместимость и наблюдаемость
- Новые optional поля
CategoryItem — additive; удаление/переименование/смена типа — breaking (migration +
rollout фронта). Enum PostAuthorType сериализуется строкой; клиент обязан игнорировать неизвестные
значения (unknown enum handling — показать код без бейджа).
- Logs:
categories-listed (OnlyEligible, ActorUserType, count; PII/суммы отсутствуют); metrics:
news_public_operations_total{operation="list-categories"}, latency histogram; traces — сквозной trace
через traceparent; dashboard news-overview.
Acceptance Criteria
- Полный список для анонима. Дано: в справочнике есть категории со
Status = Active и Archived. Когда: Anonymous вызывает GET /api/v1/categories без onlyEligible. Тогда: 200, в Items — только Active-категории в порядке SortOrder ↑, у каждой CanPublish = false; Archived-категории отсутствуют.
- Eligibility авторизованного. Дано:
User с ActorUserType = DeveloperManager. Когда: GET без фильтра. Тогда: CanPublish = true ровно у категорий, чей AllowedAuthorTypes содержит DeveloperManager (тот же предикат, что INV-CAT-002), у прочих — false.
- Форма публикации. Дано:
User с ActorUserType = Agent. Когда: GET ?onlyEligible=true. Тогда: в Items только категории с CanPublish = true, порядок SortOrder сохранён.
- Аноним + onlyEligible. Дано:
Anonymous. Когда: GET ?onlyEligible=true. Тогда: 200, Items = [], TotalCount = 0 — пустой результат, не ошибка.
- Некорректный флаг. Дано: любой актор. Когда:
onlyEligible = нераспознанный литерал. Тогда: 400 ValidationError, тело справочника не отдаётся.
- Просроченный токен. Дано: передан невалидный/просроченный JWT. Когда:
GET. Тогда: 401 (аноним без токена при этом остаётся валидным сценарием).
- Свежесть после admin-правки. Дано: администратор изменил
Title/SortOrder категории. Когда: клиент читает из HTTP-кэша фронта/CDN. Тогда: возможна задержка до TTL/до admin-инвалидации — новое значение гарантированно видно после истечения кэша (изменения редки, ADR-0056).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы