Кастомизация профиля (о себе, аватар) и просмотр профилей других игроков + общая статистика
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
"""Роутер текущего пользователя."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.auth.deps import get_current_user
|
||||
from app.core.errors import NotFoundError, ValidationError
|
||||
from app.db.session import get_session
|
||||
from app.models import User
|
||||
from app.schemas import api as s
|
||||
@@ -12,6 +14,8 @@ from app.services import audit_service, group_service, stats_service, user_servi
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
_AVATAR_MAX_BYTES = 2 * 1024 * 1024 # 2 МБ
|
||||
|
||||
|
||||
def build_me(session: Session, user: User) -> s.MeRead:
|
||||
groups = [
|
||||
@@ -25,6 +29,8 @@ def build_me(session: Session, user: User) -> s.MeRead:
|
||||
auth_provider=user.auth_provider,
|
||||
telegram_id=user.telegram_id,
|
||||
active_group_id=user.active_group_id,
|
||||
bio=user.bio,
|
||||
avatar_url=user_service.avatar_url_for(user.id, user.avatar_path, user.updated_at), # type: ignore[arg-type]
|
||||
groups=groups,
|
||||
)
|
||||
|
||||
@@ -63,9 +69,84 @@ def update_me(
|
||||
auth_provider=user.auth_provider,
|
||||
telegram_id=user.telegram_id,
|
||||
active_group_id=user.active_group_id,
|
||||
bio=user.bio,
|
||||
avatar_url=user_service.avatar_url_for(user.id, user.avatar_path, user.updated_at), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/me/profile", response_model=s.MeRead)
|
||||
def update_my_profile(
|
||||
body: s.ProfileUpdate,
|
||||
request: Request,
|
||||
session: Session = Depends(get_session),
|
||||
user: User = Depends(get_current_user),
|
||||
) -> s.MeRead:
|
||||
user_service.update_bio(session, user, body.bio)
|
||||
audit_service.record(
|
||||
session,
|
||||
actor_id=user.id,
|
||||
action="update",
|
||||
entity_type="user",
|
||||
entity_id=user.id,
|
||||
payload={"bio": True},
|
||||
ip=request.client.host if request.client else None,
|
||||
)
|
||||
session.commit()
|
||||
return build_me(session, user)
|
||||
|
||||
|
||||
@router.put("/me/avatar", response_model=s.MeRead)
|
||||
def upload_my_avatar(
|
||||
file: UploadFile = File(...),
|
||||
session: Session = Depends(get_session),
|
||||
user: User = Depends(get_current_user),
|
||||
) -> s.MeRead:
|
||||
content = file.file.read(_AVATAR_MAX_BYTES + 1)
|
||||
if len(content) > _AVATAR_MAX_BYTES:
|
||||
raise ValidationError("Файл слишком большой (макс. 2 МБ).")
|
||||
ext = user_service.sniff_image_ext(content)
|
||||
if ext is None:
|
||||
raise ValidationError("Поддерживаются только изображения PNG, JPEG или WebP.")
|
||||
user_service.set_avatar(session, user, content, ext)
|
||||
return build_me(session, user)
|
||||
|
||||
|
||||
@router.delete("/me/avatar", response_model=s.MeRead)
|
||||
def delete_my_avatar(
|
||||
session: Session = Depends(get_session),
|
||||
user: User = Depends(get_current_user),
|
||||
) -> s.MeRead:
|
||||
user_service.clear_avatar(session, user)
|
||||
return build_me(session, user)
|
||||
|
||||
|
||||
@router.get("/{user_id}/avatar")
|
||||
def get_user_avatar(
|
||||
user_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_user: User = Depends(get_current_user),
|
||||
) -> FileResponse:
|
||||
target = session.get(User, user_id)
|
||||
path = user_service.avatar_abs_path(target.avatar_path) if target else None
|
||||
if path is None or not path.exists():
|
||||
raise NotFoundError("Аватар не найден.")
|
||||
ext = path.suffix.lstrip(".")
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type=user_service.avatar_media_type(ext),
|
||||
headers={"Cache-Control": "public, max-age=3600"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{user_id}/profile", response_model=s.PublicProfile)
|
||||
def get_user_profile(
|
||||
user_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
return user_service.public_profile(session, user_id)
|
||||
|
||||
|
||||
@router.get("/me/stats", response_model=s.ProfileStats)
|
||||
def my_stats(
|
||||
session: Session = Depends(get_session),
|
||||
|
||||
Reference in New Issue
Block a user