f1tness_parser/backend/app/api/v1/exercises.py

95 lines
3.4 KiB
Python

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from app.api.deps import get_current_user, get_training_repository
from app.core.database.repository import TrainingRepository
from app.core.dto.training import ExerciseNameDTO
from app.core.dto.user import UserDTO, UserRole
from app.core.metrics import Record, summarize
from app.schemas.exercises import (
ExerciseProgressResponse,
RecordResponse,
SessionMetricsResponse,
SessionRowResponse,
)
router = APIRouter()
def stats_trainee_id(user: UserDTO, trainee_id: Optional[int]) -> Optional[int]:
"""Whose trainings the statistics are about: a trainee's own unless another one is asked
for (which, like everything else, is limited to the trainings the user may see)."""
if trainee_id is None and user.role == UserRole.trainee:
return user.id
return trainee_id
def record_response(record: Optional[Record]) -> Optional[RecordResponse]:
return RecordResponse(value=record.value, date=record.date) if record else None
@router.get("/", response_model=List[ExerciseNameDTO])
async def list_exercise_names(
trainee_id: Optional[int] = None,
user: UserDTO = Depends(get_current_user),
repository: TrainingRepository = Depends(get_training_repository),
):
"""Distinct exercise names with the number of times each was done."""
return await repository.list_exercise_names(
viewer=user, trainee_id=stats_trainee_id(user, trainee_id)
)
@router.get("/progress/", response_model=ExerciseProgressResponse)
async def exercise_progress(
name: str,
trainee_id: Optional[int] = None,
user: UserDTO = Depends(get_current_user),
repository: TrainingRepository = Depends(get_training_repository),
):
"""The whole history of the exercise with progress metrics.
The name is matched exactly, but case-insensitively: what was typed may differ in case.
"""
trainee_id = stats_trainee_id(user, trainee_id)
names_by_casefold = {
item.name.casefold(): item.name
for item in await repository.list_exercise_names(
viewer=user, trainee_id=trainee_id
)
}
exact_name = names_by_casefold.get(name.strip().casefold())
if exact_name is None:
raise HTTPException(status_code=404, detail="Exercise not found")
progress = summarize(
await repository.exercise_history(
exact_name, viewer=user, trainee_id=trainee_id
)
)
return ExerciseProgressResponse(
name=exact_name,
weighted=progress.weighted,
max_weight=progress.max_weight,
best_primary=record_response(progress.best_primary),
best_secondary=record_response(progress.best_secondary),
first_primary=progress.first_primary,
last_primary=progress.last_primary,
primary_change=progress.primary_change,
primary_change_percent=progress.primary_change_percent,
rows=[
SessionRowResponse(
training_id=row.session.training_id,
trainee_id=row.session.trainee_id,
date=row.session.date,
trainer=row.session.trainer,
splitted_weight=row.session.splitted_weight,
approaches=row.session.approaches,
metrics=SessionMetricsResponse.model_validate(
row.metrics, from_attributes=True
),
)
for row in progress.rows
],
)