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

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). Вызывает любой авторизованный пользователь для своего ЛК.

Metadata

ПолеЗначение
Сервис/контекстprofile
API areapublic
Feature groupprofile
Статусdraft
Документdocs/06-services/profile/api/public/update-my-settings.md

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

ПроверкаЗначение
Actor typeUser (Bearer JWT) — все типы пользователей
Auth policy / Permissionsauthenticated; permission profile.personal.manage-self
Scope (RBAC)self (платформенный, вне tenant)
Record-levelтолько собственная запись: ProfileEntity.Id = UserId из sub
Tenant isolationне применяется (ADR-0052)
Auditupdate: UpdatedByUserId = UserId, Version++, событие ProfileSettingsChangedEvent

HTTP-контракт

ПолеЗначение
MethodPUT
Route/api/v1/profile/me/settings
Success status200
Idempotency headerне применяется (PUT идемпотентен по значению)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса (body UpdateMySettingsForm)

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный JWT401 без токена
localebodystringДаIETF-tag из списка ru-RU / en-US (дом — profile.md)не PII
timezonebodystring?Нет≤ 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
ValidationError400Недопустимый locale / невалидный IANA tzперечень невалидных полейno retry
PROFILE_NOT_FOUND404Профиль не создан консюмером identity«Профиль ещё создаётся, повторите»retry с backoff
PROFILE_USER_BLOCKED400Status != Active (INV-PRF-3)«Учётная запись заблокирована»no retry
PROFILE_CONCURRENCY_CONFLICT409Проигрыш 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 из этой операции не публикуется.

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

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

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