POST /api/admin/notifications/templates — Создать шаблон уведомления
Назначение
Администратор платформы заводит черновик шаблона уведомления
(NotificationTemplateEntity) для пары (Kind, Locale): набор
плейсхолдеров и тело на каждый канал (ChannelTemplateEntity). Шаблон создаётся в статусе Draft;
публикация — отдельной операцией update-template (Publish). Owning-контекст —
notifications, feature group templates.
| Поле | Значение |
|---|
| Сервис/контекст | notifications |
| API area | admin |
| Feature group | templates |
| Статус | draft |
| Документ | docs/06-services/notifications/api/admin/create-template.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) |
| Auth policy / Permissions | notifications.templates.manage |
| Scope (RBAC) | платформа (каталог шаблонов) |
| Tenant isolation | не tenanted (платформенный каталог, ADR-0052 п.3) |
| Audit | create (CreatedByUserId) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/admin/notifications/templates |
| Success status | 201 |
| Idempotency header | не применяется |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
Kind | body | NotificationKind | Да | значение enum | — |
Locale | body | string | Да | BCP-47 из поддерживаемых | — |
Name | body | string | Да | 1..128 | — |
Description | body | string? | Нет | <=512 | — |
DefaultUrgency | body | NotificationUrgency? | Нет | default Normal | — |
Placeholders | body | IReadOnlyList<NotificationPlaceholder> | Да | непустой; допустимые для Kind | — |
ChannelBodies | body | IReadOnlyList<ChannelBodyInput> | Да | ≥ 1; уникальны по Channel | — |
ChannelBodyInput — { Channel (Channel), Subject? (string), Title? (string), Body (string), Format (TemplateBodyFormat), IsEnabled (bool) }; плейсхолдеры тела ⊆ Placeholders (INV-TPL-2).
Модель ответа TemplateModel
Проекция NotificationTemplateEntity: TemplateId (Guid), Kind,
Locale, Code ("{Kind}:{Locale}"), Name, Status (Draft), Placeholders, Channels
(IReadOnlyList<ChannelBodyModel>), Version, CreatedAt.
Алгоритм
- Контекст и доступ. Операцию выполняет администратор платформы; требуется право
notifications.templates.manage; каталог шаблонов платформенный (не tenanted). Нет права → FORBIDDEN.
- Проверка входа.
Kind/Locale/Channel — допустимые значения enum; плейсхолдеры, использованные в
теле, входят в объявленный Placeholders (INV-TPL-2, иначе → NTF_TEMPLATE_UNKNOWN_PLACEHOLDER); задан
хотя бы один ChannelBody, ChannelBodies уникальны по Channel. Прочие нарушения формата/ограничений
→ VALIDATION_FAILED.
- Проверка уникальности. Шаблона с
Code = "{Kind}:{Locale}" ещё не должно существовать; если он уже
есть → NTF_TEMPLATE_DUPLICATE.
- Создание. Завести шаблон в статусе
Draft с вычисленным Code и телом на каждый указанный канал.
- Согласованность. Шаблон вместе с телами каналов сохраняется в одном согласованном изменении; кэш
резолва шаблонов инвалидируется (сигнал
TemplateChanged).
- Результат. Возвращается карточка созданного шаблона (
Status = Draft), HTTP 201.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
FORBIDDEN | 403 | нет notifications.templates.manage | «Недостаточно прав» | no retry |
VALIDATION_FAILED | 400 | enum/тело/каналы вне ограничений | «Проверьте шаблон» | no retry |
NTF_TEMPLATE_UNKNOWN_PLACEHOLDER | 400 | плейсхолдер тела вне Placeholders (INV-TPL-2) | «Недопустимый плейсхолдер» | no retry |
NTF_TEMPLATE_DUPLICATE | 409 | шаблон (Kind, Locale) уже есть | «Шаблон уже существует» | no retry |
Пред- и постусловия
- Предусловие. Актор авторизован и имеет
notifications.templates.manage; шаблона с Code = "{Kind}:{Locale}" ещё нет; Kind/Locale/каналы — допустимые значения; тела содержат только плейсхолдеры
из объявленного Placeholders.
- Постусловие (успех). Создан шаблон в
Status = Draft (Version = 1, CreatedByUserId = актор) с
телами на каждый указанный канал; кэш резолва инвалидирован. Draft не участвует в резолве (INV-TPL-5):
до публикации (update-template.md → Active) enqueue/консюмеры для этого Kind/
Locale его не выбирают — если действующего шаблона нет, они получат NTF_TEMPLATE_NOT_RESOLVED.
Валидации (полностью)
Kind ∈ enum NotificationKind; Locale — поддерживаемый BCP-47; Name 1..128; Description <=512.
Placeholders непуст и состоит из значений NotificationPlaceholder, допустимых для данного Kind
(подмножество, задаваемое PayloadModel источника).
ChannelBodies ≥ 1, уникальны по Channel; каждый Channel ∈ enum; Format ∈ {Text,Html,Markdown}.
- Все плейсхолдеры
{...}, встречающиеся в Subject/Title/Body, входят в Placeholders (INV-TPL-2),
иначе NTF_TEMPLATE_UNKNOWN_PLACEHOLDER. Любое иное нарушение формата/ограничений → VALIDATION_FAILED.
- Уникальность
Code (INV-TPL-1): гонка двух параллельных создателей одной пары (Kind, Locale) — один
побеждает, второй получает NTF_TEMPLATE_DUPLICATE (страхуется уникальным ключом Code).
Совместимость и наблюдаемость
- Новые каналы/плейсхолдеры — additive. Logs:
template-created (Code, Kind); metrics:
notifications_templates_created_total.
Acceptance criteria
- Given админ и отсутствующий
(Kind, Locale), when создаёт шаблон с ≥ 1 каналом и корректными
плейсхолдерами, then 201, Status = Draft, Version = 1; шаблон не резолвится при enqueue до
публикации.
- Given шаблон для
(Kind, Locale) уже существует, when создание, then 409
NTF_TEMPLATE_DUPLICATE.
- Given тело содержит плейсхолдер вне
Placeholders, when создание, then 400
NTF_TEMPLATE_UNKNOWN_PLACEHOLDER, шаблон не создан.
- Given пустой
ChannelBodies или дубликат Channel, when создание, then 400
VALIDATION_FAILED.
- Given актор без
notifications.templates.manage, when создание, then 403 FORBIDDEN.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы