PUT /api/admin/notifications/templates/{templateId} — Изменить/опубликовать шаблон
Назначение
Администратор платформы редактирует шаблон уведомления
(NotificationTemplateEntity) — тело каналов, плейсхолдеры,
имя/описание — и меняет его статус (Draft ↔ Active, то есть публикацию и снятие с публикации) с проверкой
инвариантов (INV-TPL-1..3). Owning-контекст — notifications, feature group templates.
| Поле | Значение |
|---|
| Сервис/контекст | notifications |
| API area | admin |
| Feature group | templates |
| Статус | draft |
| Документ | docs/06-services/notifications/api/admin/update-template.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) |
| Auth policy / Permissions | notifications.templates.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | не tenanted (платформенный каталог) |
| Audit | update (Version ++) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT |
| Route | /api/admin/notifications/templates/{templateId} |
| Success status | 200 |
| Idempotency header | не применяется |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
TemplateId | route | Guid | Да | существует | — |
Name | body | string? | Нет | 1..128 | — |
Description | body | string? | Нет | <=512 | — |
DefaultUrgency | body | NotificationUrgency? | Нет | enum | — |
Placeholders | body | IReadOnlyList<NotificationPlaceholder>? | Нет | допустимые для Kind | — |
ChannelBodies | body | IReadOnlyList<ChannelBodyInput>? | Нет | уникальны по Channel; плейсхолдеры ⊆ Placeholders | — |
TargetStatus | body | TemplateStatus? | Нет | Draft/Active (переход); архив — archive-template.md | — |
ExpectedVersion | body | long? | Нет | optimistic concurrency | — |
Kind/Locale/Code иммутабельны (для смены — новый шаблон). ChannelBodyInput — см.
create-template.md.
Модель ответа TemplateModel
Та же модель, что в create-template.md (после применения; Status актуальный).
Алгоритм
- Контекст и доступ. Операцию выполняет администратор платформы; требуется право
notifications.templates.manage. Нет права → FORBIDDEN.
- Проверка входа и предусловий. Шаблон с
TemplateId существует; иначе — NTF_TEMPLATE_NOT_FOUND.
Если задан ExpectedVersion и он не совпадает с текущей версией — NTF_TEMPLATE_CONFLICT.
Kind/Locale/Code менять нельзя (попытка → VALIDATION_FAILED). Плейсхолдеры тела входят в
Placeholders (INV-TPL-2, иначе → NTF_TEMPLATE_UNKNOWN_PLACEHOLDER). Прочие нарушения формата/каналов
→ VALIDATION_FAILED.
- Правила и переходы. Применить изменения имени/описания/плейсхолдеров/тела каналов. При переходе в
Active (публикация) действуют инварианты: должен быть хотя бы один включённый канал с телом (INV-TPL-3,
иначе → NTF_TEMPLATE_NO_CHANNEL_BODY); на пару (Kind, Locale) допустим лишь один Active-шаблон
(INV-TPL-1); системный шаблон здесь не архивируется (INV-TPL-4 — только через
archive-template.md).
- Согласованность. Изменения шаблона и его тел каналов сохраняются в одном согласованном изменении;
правка не должна затирать чужую (при конфликте версий —
NTF_TEMPLATE_CONFLICT); кэш резолва шаблонов
инвалидируется (сигнал TemplateChanged). Уже отправленные уведомления хранят собственный RenderedBody
и не меняются.
- Результат. Возвращается карточка шаблона с актуальным
Status.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
FORBIDDEN | 403 | нет notifications.templates.manage | «Недостаточно прав» | no retry |
NTF_TEMPLATE_NOT_FOUND | 404 | TemplateId не найден | «Шаблон не найден» | no retry |
VALIDATION_FAILED | 400 | попытка сменить Kind/Locale; тело/каналы вне ограничений | «Проверьте шаблон» | no retry |
NTF_TEMPLATE_UNKNOWN_PLACEHOLDER | 400 | плейсхолдер вне Placeholders | «Недопустимый плейсхолдер» | no retry |
NTF_TEMPLATE_NO_CHANNEL_BODY | 422 | Active без включённого канала с телом (INV-TPL-3) | «Нужно тело хотя бы одного канала» | no retry |
NTF_TEMPLATE_CONFLICT | 409 | ExpectedVersion не совпадает с текущей версией | «Повторите сохранение» | retry (перечитать) |
Переходы статуса и их следствия
Переход TargetStatus | Условие | Следствие для доставки |
|---|
Draft → Active (публикация) | ≥ 1 включённый канал с телом (INV-TPL-3); единственность (Kind, Locale) по Code (INV-TPL-1) | шаблон начинает резолвиться при enqueue/консюмерами для Kind/Locale |
Active → Draft (снятие с публикации) | — | шаблон перестаёт резолвиться; если действующего шаблона для Kind/Locale больше нет — enqueue/консюмеры получают NTF_TEMPLATE_NOT_RESOLVED (422/DLQ) |
| правка без смены статуса | плейсхолдеры/каналы валидны (INV-TPL-2) | новые уведомления рендерятся обновлённым телом; уже отправленные — неизменны (снапшот RenderedBody) |
в Archived | только через archive-template.md | здесь недоступно |
Пред- и постусловия
- Предусловие. Актор имеет
notifications.templates.manage; шаблон существует; при указании
ExpectedVersion он совпадает с текущей версией; Kind/Locale/Code не изменяются.
- Постусловие (успех). Применены правки имени/описания/плейсхолдеров/тел каналов; при переходе — новый
Status; Version увеличена; кэш резолва инвалидирован. In-flight уведомления не затронуты.
Совместимость и наблюдаемость
Kind/Locale иммутабельны. Logs: template-updated/template-published (Code, TargetStatus);
metrics: notifications_templates_updated_total.
Acceptance criteria
- Given
Draft-шаблон с ≥ 1 включённым каналом, when публикация (TargetStatus = Active), then
200, Status = Active; шаблон резолвится при enqueue соответствующего Kind.
- Given
Draft без включённого канала с телом, when публикация, then 422
NTF_TEMPLATE_NO_CHANNEL_BODY (INV-TPL-3), статус не изменён.
- Given
Active-шаблон, when снятие с публикации (TargetStatus = Draft) и это был единственный
действующий шаблон (Kind, Locale), then 200; последующий enqueue этого Kind → NTF_TEMPLATE_NOT_RESOLVED.
- Given попытка сменить
Kind/Locale, when обновление, then 400 VALIDATION_FAILED.
- Given плейсхолдер тела вне
Placeholders, when обновление, then 400
NTF_TEMPLATE_UNKNOWN_PLACEHOLDER.
- Given две конкурентные правки, when вторая приходит с устаревшим
ExpectedVersion, then 409
NTF_TEMPLATE_CONFLICT; проигравший перечитывает актуальную версию и повторяет.
- Given уже отправленные уведомления по старому телу, when шаблон отредактирован, then их
RenderedBody не меняется.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы