PUT /api/public/notifications/settings — Обновить мои настройки уведомлений
Назначение
Авторизованный пользователь меняет свои настройки уведомлений
(UserNotificationSettingsEntity) — per-Kind per-Channel
opt-in/out, тихие часы, локаль, GlobalMute. Запись создаётся при первом сохранении и полностью заменяет
набор Preferences. Виды Critical отключить нельзя — их opt-out игнорируется (INV-UNS-4). Owning-контекст —
notifications, feature group settings.
| Поле | Значение |
|---|
| Сервис/контекст | notifications |
| API area | public (JWT пользователя) |
| Feature group | settings |
| Статус | draft |
| Документ | docs/06-services/notifications/api/public/update-my-notification-settings.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User |
| Auth policy / Permissions | notifications.settings.manage (own) |
| Record-level | владелец = UserId (актор); правит только свои настройки (INV-UNS-1) |
| Tenant isolation | TenantId = компания пользователя или null, из claims (не из body) |
| Audit | update (Version) |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT |
| Route | /api/public/notifications/settings |
| Success status | 200 |
| Idempotency header | не применяется (полная замена — идемпотентна по содержимому) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (JWT sub) | Guid | Да | не Guid.Empty | владелец записи |
Locale | body | string | Нет | BCP-47 из поддерживаемых; default ru | — |
GlobalMute | body | bool | Нет | default false | — |
QuietHours | body | QuietHoursInput? | Нет | { Start, End, TimeZoneId }; TimeZoneId — валидный IANA | — |
Preferences | body | IReadOnlyList<PreferenceInput> | Нет | элементы уникальны по (Kind, Channel) (INV-UNS-2); Kind/Channel — значения enum | — |
PreferenceInput — { Kind (NotificationKind), Channel (Channel), IsEnabled (bool) }. QuietHoursInput —
{ Start (TimeOnly), End (TimeOnly), TimeZoneId (string) }.
Модель ответа NotificationSettingsModel
Та же модель, что в get-my-notification-settings.md (эффективные настройки
после применения).
Алгоритм
- Контекст и доступ. Операцию выполняет авторизованный пользователь (
UserId из JWT; нет валидного JWT
→ UNAUTHENTICATED); правит только свои настройки (INV-UNS-1).
- Проверка входа. Элементы
Preferences уникальны по (Kind, Channel) (INV-UNS-2); Kind/Channel —
допустимые значения enum; Locale из поддерживаемых; QuietHours.TimeZoneId — валидный IANA. Нарушение →
VALIDATION_FAILED.
- Применение. Если запись настроек есть — заменить набор
Preferences, обновить Locale/GlobalMute/
тихие часы; если записи нет — создать её для UserId/TenantId. Для видов Critical opt-out
игнорируется (INV-UNS-4).
- Согласованность. Агрегат настроек вместе с owned-
Preferences сохраняется в одном согласованном
изменении; правка не должна затирать конкурентную (при конфликте версий — NTF_SETTINGS_CONFLICT); кэш
резолва настроек инвалидируется (сигнал SettingsChanged).
- Результат. Возвращаются эффективные настройки после применения (та же модель, что в чтении), HTTP
200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Кэш / проекции | инвалидация кэша резолва настроек (сигнал SettingsChanged) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
UNAUTHENTICATED | 401 | нет/невалидный JWT | «Требуется авторизация» | no retry |
VALIDATION_FAILED | 400 | дубли (Kind, Channel) / невалидный Kind/Channel/TimeZoneId/Locale | «Проверьте настройки» | no retry |
NTF_SETTINGS_CONFLICT | 409 | конкурентная правка (конфликт версий) | «Повторите сохранение» | retry (перечитать) |
Семантика применения
- Полная замена
Preferences. Переданный список целиком заменяет прежний набор переопределений: пары
(Kind, Channel), отсутствующие в запросе, возвращаются к дефолту политики (их прежнее явное значение
теряется). Пустой Preferences = «сбросить все переопределения к дефолтам». Дубли (Kind, Channel) в
запросе запрещены (INV-UNS-2).
Critical неотключаем. Для видов Urgency = Critical opt-out игнорируется (INV-UNS-4): даже переданный
IsEnabled = false не отключает доставку (канал сменить можно, полностью замолчать — нет).
- Не ретроактивно. Новые настройки влияют только на уведомления, резолвящиеся после сохранения; уже
созданные
NotificationEntity (Pending/Sent) не переоцениваются.
- Тихие часы.
QuietHours — окно в локальном времени пользователя; Start > End трактуется как интервал
через полночь. TimeZoneId — валидный IANA, иначе VALIDATION_FAILED. Применяются только к
interruptive-каналам (INV-UNS-5); Critical их игнорирует.
- Первое сохранение создаёт запись для
UserId/TenantId (INV-UNS-1); последующие — обновляют.
Пред- и постусловия
- Предусловие. Пользователь авторизован; правит только свои настройки; вход валиден (уникальность
(Kind, Channel), enum, IANA, поддерживаемый Locale).
- Постусловие (успех). Запись настроек создана/обновлена (
Version ++), Preferences заменены,
Locale/GlobalMute/QuietHours применены (с учётом INV-UNS-4); кэш резолва инвалидирован; возвращена
эффективная модель.
Совместимость и наблюдаемость
- Новые виды
Kind/каналы — additive (появляются в дефолтах). Удаление enum-значений — breaking.
- Logs:
settings-updated (UserId, число Preferences); metrics:
notifications_settings_updated_total.
Acceptance criteria
- Given пользователь без записи настроек, when сохраняет
Preferences+QuietHours, then 200,
запись создана, возвращены эффективные настройки.
- Given ранее заданное переопределение, отсутствующее в новом запросе, when сохранение, then эта
пара
(Kind, Channel) вернулась к дефолту (IsDefault = true в чтении).
- Given попытка opt-out для
Critical-вида, when сохранение, then 200, но доставка Critical
сохранена (INV-UNS-4).
- Given дубль
(Kind, Channel) или невалидный TimeZoneId/Locale/Kind/Channel, when сохранение,
then 400 VALIDATION_FAILED.
- Given конкурентная правка с устаревшей версией, when сохранение, then
409
NTF_SETTINGS_CONFLICT; проигравший перечитывает и повторяет.
- Given отсутствует/просрочен JWT, when сохранение, then
401 UNAUTHENTICATED.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы