"""Профиль пользователя: сборка ответа, правка данных, аватар. Переиспользуется `api/users.py` (свой профиль) и `api/admin.py` (карточка профиля любого пользователя администратором) — правила одни и те же (задача 4c: «те же данные, редактирование по тем же правилам»). """ import uuid from pathlib import Path from fastapi import UploadFile from sqlalchemy.ext.asyncio import AsyncSession from models.team import Team from models.user import User from repositories.admin import TeamRepository from services.avatars import delete_avatar, read_and_validate_avatar, save_avatar class TeamNotFoundError(Exception): """Команда с указанным `team_id` не найдена.""" async def resolve_team_name(session: AsyncSession, team_id: uuid.UUID | None) -> str | None: """Имя команды пользователя (`None`, если команда не привязана или была удалена).""" if team_id is None: return None team = await session.get(Team, team_id) return team.name if team is not None else None async def update_profile_fields( session: AsyncSession, user: User, *, name_user: str | None, team_id: uuid.UUID | None, team_id_is_set: bool, ) -> User: """Изменить ФИО и/или команду пользователя. `team_id_is_set` — поле присутствовало в теле запроса (в т.ч. явный `null`, различаем через `model_fields_set` на стороне роутера) — тот же приём, что и `summary_recipients`/`team_id` в других PATCH-эндпоинтах (`services/conferences.py`, `api/admin.py`). Email НЕ принимается — read-only поле профиля. """ if name_user is not None: user.name_user = name_user if team_id_is_set: if team_id is not None and await TeamRepository(session).get(team_id) is None: raise TeamNotFoundError user.team_id = team_id return user async def set_avatar(media_root: Path, user: User, file: UploadFile) -> User: """Загрузить, провалидировать и сохранить новый аватар пользователя. Бросает `AvatarTooLargeError`/`AvatarInvalidTypeError` (см. `services/avatars.py`). """ content, ext = await read_and_validate_avatar(file) user.avatar_path = save_avatar(media_root, user.id, content, ext, old_path=user.avatar_path) return user def clear_avatar(media_root: Path, user: User) -> None: """Удалить файл аватара пользователя с диска и очистить `avatar_path`.""" delete_avatar(media_root, user.avatar_path) user.avatar_path = None