PUT /api/v1/profile/me/settings — Обновление настроек ЛК (updateMySettings)
Назначение
Обновить собственные настройки отображения личного кабинета — Locale и Timezone
(profile.md). Доступно всем типам пользователей, включая менеджеров
застройщика (ContactsManagedBy = TalentSync): настройки ЛК — PIN-локальные и не реплицируются из
Talent (матрица возможностей profile.md). Смена ФИО/email/phone через этот метод
невозможна — реплика identity read-only (INV-PRF-1); для TalentSync она запрещена и в identity
(INV-IDN-001). Вызывает любой авторизованный пользователь для своего ЛК.
| Поле | Значение |
|---|
| Сервис/контекст | profile |
| API area | public |
| Feature group | profile |
| Статус | draft |
| Документ | docs/06-services/profile/api/public/update-my-settings.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Bearer JWT) — все типы пользователей |
| Auth policy / Permissions | authenticated; permission profile.personal.manage-self |
| Scope (RBAC) | self (платформенный, вне tenant) |
| Record-level | только собственная запись: ProfileEntity.Id = UserId из sub |
| Tenant isolation | не применяется (ADR-0052) |
| Audit | update: UpdatedByUserId = UserId, Version++, событие ProfileSettingsChangedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT |
| Route | /api/v1/profile/me/settings |
| Success status | 200 |
| Idempotency header | не применяется (PUT идемпотентен по значению) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса (body UpdateMySettingsForm)
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
locale | body | string | Да | IETF-tag из списка ru-RU / en-US (дом — profile.md) | не PII |
timezone | body | string? | Нет | ≤ 64 символов; валидный IANA tz id (Europe/Moscow); null — сброс | не PII |
Модель ответа MyProfileModel
Модель — дом в get-my-profile.md (без переописания); возвращается обновлённое
состояние профиля (locale, timezone, version — новые значения).
Алгоритм
Тривиальное обновление настроек одной записи профиля. Операцию выполняет авторизованный пользователь любого
типа (UserId из токена); tenant не применяется; требуется право profile.personal.manage-self. Семантика
PUT — полное желаемое состояние настроек: locale обязателен и точно (с учётом регистра) совпадает с одним
из whitelist-значений (ru-RU/en-US) — иначе ValidationError; timezone — резолвимый IANA tz id
(напр. Europe/Moscow), фиксированные смещения (+03:00), Windows-имена зон и неизвестные идентификаторы
отклоняются, а отсутствие поля или null сбрасывает часовой пояс (отображение — по умолчанию). Профиль
владельца существует; иначе PROFILE_NOT_FOUND (реплика identity ещё догоняется — retry). Профиль активен
(Status = Active); иначе PROFILE_USER_BLOCKED (INV-PRF-3). Guard TalentSync не применяется — настройки
разрешены всем типам; реплика-поля контактов/ФИО не затрагиваются (INV-PRF-1). Значения locale/timezone
записываются в профиль, UpdatedByUserId = UserId, Version++; при фактическом изменении хотя бы одного
поля фиксируется факт ProfileSettingsChangedEvent, иначе (значения не изменились) — идемпотентный no-op:
версия не растёт и событие не поднимается. Конкурентная правка из другой вкладки проигрывает по версии →
PROFILE_CONCURRENCY_CONFLICT. Результат — обновлённая модель MyProfileModel (как в
get-my-profile.md); HTTP 200. Значения PIN-локальны и наружу не транслируются (Kafka
не публикуется): ЛК читает их для языка интерфейса и отображения дат, а при локализации уведомлений сервис
notifications берёт актуальные Locale/Timezone профиля синхронно (internal read на момент отправки),
снапшот в шину не выносится.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
ValidationError | 400 | Недопустимый locale / невалидный IANA tz | перечень невалидных полей | no retry |
PROFILE_NOT_FOUND | 404 | Профиль не создан консюмером identity | «Профиль ещё создаётся, повторите» | retry с backoff |
PROFILE_USER_BLOCKED | 400 | Status != Active (INV-PRF-3) | «Учётная запись заблокирована» | no retry |
PROFILE_CONCURRENCY_CONFLICT | 409 | Проигрыш optimistic-гонки | «Данные изменены, повторите» | retry с актуальными данными |
Совместимость и наблюдаемость
- Форма расширяется только optional-полями настроек (additive); добавление полей контактов/ФИО сюда
запрещено — их дом
identity (жёсткая граница profile.md).
- Logs:
UserId, changedFields (имена полей, без значений — правило единое); metrics:
profile_api_operations_total{operation="updateMySettings",result}; traces — OTel.
Acceptance criteria
- Дано любой авторизованный пользователь (в т.ч. менеджер застройщика с
ContactsManagedBy = TalentSync) с существующим активным профилем; когда PUT settings с валидными locale и timezone; тогда значения записаны, Version++, поднято ProfileSettingsChangedEvent, HTTP 200 с новыми значениями.
- Дано переданные значения совпадают с текущими; когда PUT; тогда идемпотентный no-op: версия не растёт, событие не поднимается, HTTP
200.
- Дано
locale вне whitelist или в ином регистре; когда PUT; тогда ValidationError (400).
- Дано
timezone — не IANA tz id (фиксированное смещение / Windows-имя / неизвестная зона); когда PUT; тогда ValidationError (400); null или отсутствие поля сбрасывает часовой пояс без ошибки.
- Дано профиль ещё не создан консюмером identity (лаг реплики); когда PUT; тогда
PROFILE_NOT_FOUND (404, retry с backoff).
- Дано профиль
Blocked/Archived; когда PUT; тогда PROFILE_USER_BLOCKED (400).
- Дано параллельная правка настроек из другой вкладки; когда обе коммитятся; тогда проигравший получает
PROFILE_CONCURRENCY_CONFLICT (409).
- Дано попытка сменить ФИО/email/phone через эту операцию; когда такие поля присутствуют в теле; тогда они игнорируются/отклоняются — их дом
identity (INV-PRF-1, жёсткая граница), Kafka из этой операции не публикуется.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы