Update GPT and add Profile

This commit is contained in:
2025-12-16 22:12:12 +07:00
parent 08b96fd1f7
commit 696dc714c4
15 changed files with 1063 additions and 90 deletions

View File

@@ -1,10 +1,16 @@
from fastapi import APIRouter, HTTPException, status, UploadFile, File
from sqlalchemy import select
from sqlalchemy import select, func
from app.api.deps import DbSession, CurrentUser
from app.core.config import settings
from app.models import User
from app.schemas import UserPublic, UserUpdate, TelegramLink, MessageResponse
from app.core.security import verify_password, get_password_hash
from app.models import User, Participant, Assignment, Marathon
from app.models.assignment import AssignmentStatus
from app.models.marathon import MarathonStatus
from app.schemas import (
UserPublic, UserUpdate, TelegramLink, MessageResponse,
PasswordChange, UserStats, UserProfilePublic,
)
from app.services.storage import storage_service
router = APIRouter(prefix="/users", tags=["users"])
@@ -125,3 +131,142 @@ async def unlink_telegram(
await db.commit()
return MessageResponse(message="Telegram account unlinked successfully")
@router.post("/me/password", response_model=MessageResponse)
async def change_password(
data: PasswordChange,
current_user: CurrentUser,
db: DbSession,
):
"""Смена пароля текущего пользователя"""
if not verify_password(data.current_password, current_user.password_hash):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Неверный текущий пароль",
)
if data.current_password == data.new_password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Новый пароль должен отличаться от текущего",
)
current_user.password_hash = get_password_hash(data.new_password)
await db.commit()
return MessageResponse(message="Пароль успешно изменен")
@router.get("/me/stats", response_model=UserStats)
async def get_my_stats(current_user: CurrentUser, db: DbSession):
"""Получить свою статистику"""
return await _get_user_stats(current_user.id, db)
@router.get("/{user_id}/stats", response_model=UserStats)
async def get_user_stats(user_id: int, db: DbSession):
"""Получить статистику пользователя"""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)
return await _get_user_stats(user_id, db)
@router.get("/{user_id}/profile", response_model=UserProfilePublic)
async def get_user_profile(user_id: int, db: DbSession):
"""Получить публичный профиль пользователя со статистикой"""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)
stats = await _get_user_stats(user_id, db)
return UserProfilePublic(
id=user.id,
nickname=user.nickname,
avatar_url=user.avatar_url,
created_at=user.created_at,
stats=stats,
)
async def _get_user_stats(user_id: int, db) -> UserStats:
"""Вспомогательная функция для подсчета статистики пользователя"""
# 1. Количество марафонов (участий)
marathons_result = await db.execute(
select(func.count(Participant.id))
.where(Participant.user_id == user_id)
)
marathons_count = marathons_result.scalar() or 0
# 2. Количество побед (1 место в завершенных марафонах)
wins_count = 0
user_participations = await db.execute(
select(Participant)
.join(Marathon, Marathon.id == Participant.marathon_id)
.where(
Participant.user_id == user_id,
Marathon.status == MarathonStatus.FINISHED.value
)
)
for participation in user_participations.scalars():
# Для каждого марафона проверяем, был ли пользователь первым
max_points_result = await db.execute(
select(func.max(Participant.total_points))
.where(Participant.marathon_id == participation.marathon_id)
)
max_points = max_points_result.scalar() or 0
if participation.total_points == max_points and max_points > 0:
# Проверяем что он единственный с такими очками (не ничья)
count_with_max = await db.execute(
select(func.count(Participant.id))
.where(
Participant.marathon_id == participation.marathon_id,
Participant.total_points == max_points
)
)
if count_with_max.scalar() == 1:
wins_count += 1
# 3. Выполненных заданий
completed_result = await db.execute(
select(func.count(Assignment.id))
.join(Participant, Participant.id == Assignment.participant_id)
.where(
Participant.user_id == user_id,
Assignment.status == AssignmentStatus.COMPLETED.value
)
)
completed_assignments = completed_result.scalar() or 0
# 4. Всего очков заработано
points_result = await db.execute(
select(func.coalesce(func.sum(Assignment.points_earned), 0))
.join(Participant, Participant.id == Assignment.participant_id)
.where(
Participant.user_id == user_id,
Assignment.status == AssignmentStatus.COMPLETED.value
)
)
total_points_earned = points_result.scalar() or 0
return UserStats(
marathons_count=marathons_count,
wins_count=wins_count,
completed_assignments=completed_assignments,
total_points_earned=total_points_earned,
)