"""Pydantic-схемы для аутентификации и профиля пользователя.""" import uuid from pydantic import BaseModel, ConfigDict, EmailStr, Field class RegisterIn(BaseModel): """Тело запроса регистрации нового пользователя. `team_id` допустим только при включённой настройке инстанса `registration_team_choice` (см. `GET /auth/registration-options`) и существующей команде — иначе `POST /auth/register` вернёт 400. `consent_accepted` обязан быть `True`, если в настройках инстанса включено `consent_required` (согласие на обработку персональных данных) — иначе `POST /auth/register` вернёт 400. Игнорируется, если настройка выключена (второй эшелон проверки — фронт тоже блокирует кнопку, но сервер не полагается на это). """ email: EmailStr name_user: str = Field(min_length=1, max_length=255) password: str = Field(min_length=8) team_id: uuid.UUID | None = None consent_accepted: bool = False class VerifyEmailIn(BaseModel): """Тело запроса подтверждения email.""" token: str = Field(min_length=1) class TokenOut(BaseModel): """Ответ с access-токеном; refresh-токен передаётся отдельно в httpOnly cookie.""" access_token: str token_type: str = "bearer" class UserOut(BaseModel): """Публичное представление пользователя (профиль текущего пользователя). `email: str`, а не `EmailStr` — намеренно: это response-схема, отражающая уже сохранённые в БД данные, а не принимающая новый ввод. `EmailStr` дополнительно отсекает синтаксически валидные, но зарезервированные домены (`.local`, `.test` и т.п. из RFC 6761) — валидный email на входе (`RegisterIn`, ниже) мог быть заведён напрямую в БД (seed, ручная миграция) с таким доменом; строгая `EmailStr` на выходе привела бы к 500 `ResponseValidationError` для уже существующих пользователей. """ model_config = ConfigDict(from_attributes=True) id: uuid.UUID email: str name_user: str role: str class UserListItemOut(BaseModel): """Элемент списка пользователей (пикер участников конференции/мультиселект брони).""" id: uuid.UUID display_name: str avatar_url: str | None = None class ProfileUpdateIn(BaseModel): """Тело правки профиля текущего пользователя. Email НЕ принимается — read-only поле профиля. `team_id` не различает «поле не передано» и «явный `null`» через сравнение с `None` — роутер читает `model_fields_set` (тот же приём, что `AdminUserUpdateIn.team_id`). """ name_user: str | None = Field(default=None, min_length=1, max_length=255) team_id: uuid.UUID | None = None class UserProfileOut(UserOut): """Профиль пользователя (свой либо открытый администратором) — `UserOut` + аватар/команда.""" avatar_url: str | None = None team_id: uuid.UUID | None = None team_name: str | None = None class PasswordChangeIn(BaseModel): """Тело смены пароля текущим пользователем (`POST /users/me/password`). Политика сложности `new_password` — та же, что при регистрации (`RegisterIn.password`, min 8 символов). """ current_password: str new_password: str = Field(min_length=8) class RegistrationTeamOptionOut(BaseModel): """Команда в списке опций публичной карточки регистрации.""" id: uuid.UUID name: str class RegistrationOptionsOut(BaseModel): """Публичные опции формы регистрации (`GET /auth/registration-options`). `teams` отдаётся только при `team_choice_enabled=True` — иначе пустой список (справочник команд не раскрывается, пока выбор выключен). `email_domains` — эталонные домены при включённой верификации регистрации по домену email (настройка инстанса `registration_email_domain`), иначе пустой список. """ team_choice_enabled: bool teams: list[RegistrationTeamOptionOut] email_domains: list[str] = Field(default_factory=list) # Согласие на обработку персональных данных: `consent_required` — обязательна # ли галочка на форме регистрации; `consent_text`/`consent_version` отдаются # ВСЕГДА, независимо от `consent_required` — той же строкой пользуется # публичная страница регламента, доступная и при выключенном модуле. consent_required: bool = False consent_text: str = "" consent_version: int = 1