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

GET /api/public/support/kb/articles/{slug} — Статья базы знаний

Публичное получение одной статьи справки/FAQ по Slug для витрины помощи ПИН (/help/{slug}). Отдаёт только опубликованную статью, доступную актору по аудитории; иначе — 404 (существование скрытого/чернового контента не раскрывается). Проекция статьи KnowledgeBaseArticleEntity — дом полей в knowledge-base-article.md; здесь описана только public-дельта. Списочная/поисковая выдача — search-kb-articles.

Назначение

Дать гостю или авторизованному пользователю прочитать конкретную статью базы знаний по человекочитаемому Slug (матрица 4.7 «база знаний для пользователей», 7.1/7.2). Побочно — приблизительно учесть популярность статьи (ViewsCount, INV-KB-4) без влияния на ответ.

Metadata

ПолеЗначение
Сервис/контекстsupport
API areapublic
Feature groupknowledge-base
Статусdraft
Документdocs/06-services/support/api/public/get-kb-article.md

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

ПроверкаЗначение
Actor typeAnonymous (гость) или User (любой авторизованный по JWT)
Auth policy / Permissionsnone — публичное чтение; доступ решается audience-gate (шаг 4), не permission
Scope (RBAC)не применяется (актор — вне platform-scope; support.kb.view нужен только для support-области)
Record-levelaudience-gate по Audience статьи vs тип актора (INV-KB-3)
Tenant isolationTenantId = PIN CompanyId — single-tenant platform-tool (ADR-0052); резолвится сервером, не приходит из запроса
Auditread (не sensitive); инкремент ViewsCount — не аудит-событие

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/public/support/kb/articles/{slug}
Success status200
Idempotency headerне применяется (чтение; ViewsCount — best-effort, приблизительный)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
SlugroutestringДаkebab-case ^[a-z0-9]+(?:-[a-z0-9]+)*$, 1..160 (ICodeEntity)
ActorTypeidentity claimsActorType (enum строкой)НетAnonymous по умолчанию, User при валидном JWTвход для audience-gate
TenantIdserver (platform)GuidДа= PIN CompanyId; не из запроса (ADR-0052)tenant фиксирован

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

Проекция KnowledgeBaseArticleEntity (дом полей — knowledge-base-article.md); здесь только public-дельта. Возвращается один объект (не список).

ПолеТипОбязательностьИсточникMaskingОписание
TitlestringДаentity.TitleЗаголовок статьи.
Summarystring?Нетentity.SummaryКраткое описание/анонс.
BodystringДаentity.BodyТело (markdown/html); непустое гарантировано публикацией (INV-KB-2).
CategoryKbCategoryДаentity.CategoryКатегория аудитории; сериализация строкой (enum home — domain).
TagsKbTagsДаentity.TagsТеги; коллекция живёт в именованном контейнере KbTags { Items } (jsonb, ContractVersion = 1).
PublishedAtDateTimeOffsetДаentity.PublishedAtМомент публикации; non-null, т.к. отдаётся только Published.

Id, Slug, Status, Audience, ViewsCount и аудит-поля наружу не возвращаются (внутренние/служебные).

Алгоритм

  1. Контекст и доступ. Читателем может быть гость (Anonymous) или любой авторизованный пользователь (User, JWT опционален). support — единый платформенный tenant (TenantId = PIN CompanyId, резолвится сервером). Slug — из маршрута. Permission не требуется; полнота доступа решается audience-gate (шаг 3).

  2. Проверка входа. Slug не пустой, kebab-case, длина 1..160; нарушение → VALIDATION_FAILED.

  3. Поиск статьи и audience-gate. Статья ищется по Slug среди живых (удалённые исключены). Любой из отказов ниже схлопывается в SUPPORT_KB_NOT_FOUND (404) — существование скрытого контента не раскрывается:

    • статья не найдена → 404;
    • состояние: Status == Published, иначе → 404;
    • аудитория (INV-KB-3): Audience == Public → любой актор; Audience == AuthenticatedUsers → только авторизованный User; Audience == SupportInternal → на public не отдаётся никогда → 404. Отдельный код SUPPORT_KB_AUDIENCE_FORBIDDEN (403) наружу не выводится — чтобы не раскрывать наличие статьи.

    При успехе возвращаемые поля берутся напрямую из статьи: Title, Summary, Body, Category (enum строкой), Tags (KbTags), PublishedAt.

  4. Best-effort учёт популярности. После успешного gate счётчик ViewsCount инкрементируется приблизительно (INV-KB-4): побочно, вне критического пути ответа, без блокировок и доменных событий; ошибка инкремента логируется (warn) и глотается, на ответ не влияет. Идемпотентность не требуется — счётчик приблизительный.

  5. Результат. Возвращается KbArticleModel → HTTP 200 + body. Любой отказ (не найдена / не Published / аудитория не совпала) → SUPPORT_KB_NOT_FOUND404.

Диаграмма

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

ТипДетали
Integration eventsnone (read; KB не публикует Kafka-событий)
External callsnone
Cache / projections / searchbest-effort инкремент support.kb_articles.views_count (приблизительный, INV-KB-4); read-cache не применяется в первой версии (низкий объём, ADR-0056)
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400пустой/некорректный Slug (формат)«Проверьте адрес статьи»no retry
SUPPORT_KB_NOT_FOUND404статья не найдена, либо Status != Published, либо аудитория не совпала (схлопнуто)«Статья не найдена»no retry

Acceptance criteria

  • AC-1 (публичная статья). Given статья Status = Published, Audience = Public; When GET .../kb/articles/{slug} гостем (Anonymous); Then 200 с Title/Summary/Body/Category/ Tags/PublishedAt; ViewsCount инкрементируется best-effort (вне ответа).
  • AC-2 (только авторизованным). Given статья Audience = AuthenticatedUsers; When запрос гостем → SUPPORT_KB_NOT_FOUND (404); When тот же запрос авторизованным User200 (INV-KB-3).
  • AC-3 (внутренняя статья скрыта). Given статья Audience = SupportInternal; When любой public-запрос; Then SUPPORT_KB_NOT_FOUND (404) — на public не отдаётся никогда, факт наличия не раскрывается (код SUPPORT_KB_AUDIENCE_FORBIDDEN наружу не выводится).
  • AC-4 (не опубликована). Given статья Status ∈ {Draft, Archived} или soft-deleted; When запрос по Slug; Then 404 (схлопнуто в SUPPORT_KB_NOT_FOUND).
  • AC-5 (нет статьи). Given несуществующий Slug; When запрос; Then 404.
  • AC-6 (формат slug). Given Slug пустой/не kebab-case/>160; When запрос; Then VALIDATION_FAILED (400).
  • AC-7 (учёт просмотра не влияет на ответ). Given статья доступна, но инкремент ViewsCount упал; When запрос; Then ответ всё равно 200 с телом; ошибка инкремента логируется (warn) и глотается (INV-KB-4, best-effort).

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

  • Добавление optional-полей в KbArticleModel — additive (не breaking); удаление/переименование/смена типа — breaking (migration + rollout). Category сериализуется строкой — клиент обязан толерантно обрабатывать неизвестные значения enum (forward-compat).
  • Logs: kb-article served / kb-article not-found (Slug, result; причину gate не раскрывать в 404-теле); metrics: support_kb_article_reads_total{result}, latency p95 < 300 мс; traces — OTel; dashboard Grafana pin-support; runbook — docs/06-services/support/runbooks/ (будущий путь по конвенции).

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

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

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