Pre-fill the reps and weight inputs with progression engine suggestions so users can log sets without manually retyping values each time. Suggestions flow through the template chain on initial page load and on all HTMX partial responses (log, edit, delete).
203 lines
6.4 KiB
Python
203 lines
6.4 KiB
Python
"""Workout logging routes for inline set tracking.
|
|
|
|
Handles creating, editing, and deleting individual set logs.
|
|
All responses are HTMX partials that update in place.
|
|
"""
|
|
|
|
from datetime import date
|
|
|
|
import structlog
|
|
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from sqlmodel import Session
|
|
|
|
from app.database import get_db_session
|
|
from app.models.user import User
|
|
from app.services.log_service import LogService
|
|
from app.services.progression_service import ProgressionService
|
|
from app.services.workout_session_service import WorkoutSessionService
|
|
from app.utils.auth import get_current_admin_user, get_active_profile_id
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
router = APIRouter(prefix="/log", tags=["logging"])
|
|
|
|
|
|
@router.post("", response_class=HTMLResponse)
|
|
async def log_set(
|
|
request: Request,
|
|
session: Session = Depends(get_db_session),
|
|
admin: User = Depends(get_current_admin_user),
|
|
):
|
|
"""Log a single set for an exercise.
|
|
|
|
Creates the workout session if it doesn't exist yet (auto-create).
|
|
Returns the updated log entries partial for this exercise.
|
|
|
|
Args:
|
|
request: The incoming HTTP request.
|
|
session: Database session.
|
|
admin: The authenticated admin user.
|
|
|
|
Returns:
|
|
Rendered log entries partial for this exercise.
|
|
"""
|
|
form = await request.form()
|
|
exercise_id = int(form.get("exercise_id", 0))
|
|
workout_day_id = int(form.get("workout_day_id", 0))
|
|
set_number = int(form.get("set_number", 1))
|
|
reps = int(form.get("reps", 0))
|
|
weight = form.get("weight", "")
|
|
felt_easy = form.get("felt_easy") == "on"
|
|
|
|
active_profile_id = get_active_profile_id(request)
|
|
if not active_profile_id:
|
|
templates = request.app.state.templates
|
|
return templates.TemplateResponse("partials/flash_message.html", {
|
|
"request": request,
|
|
"flash_error": "No profile selected. Switch profiles first.",
|
|
})
|
|
|
|
# Get or create today's session
|
|
ws_service = WorkoutSessionService(session)
|
|
ws = ws_service.get_or_create_session(
|
|
user_id=active_profile_id,
|
|
workout_day_id=workout_day_id,
|
|
session_date=date.today(),
|
|
)
|
|
|
|
# Create the log entry
|
|
log_service = LogService(session)
|
|
log_service.create_log(
|
|
session_id=ws.id,
|
|
exercise_id=exercise_id,
|
|
set_number=set_number,
|
|
reps_completed=reps,
|
|
weight_used=weight,
|
|
felt_easy=felt_easy,
|
|
)
|
|
|
|
# Return updated logs for this exercise
|
|
logs = log_service.list_logs_for_exercise(ws.id, exercise_id)
|
|
next_set = len(logs) + 1
|
|
|
|
# Fetch suggestion for pre-filling the next set form
|
|
progression = ProgressionService(session)
|
|
suggestion = progression.get_suggestion(active_profile_id, exercise_id)
|
|
|
|
templates = request.app.state.templates
|
|
return templates.TemplateResponse("partials/log_entry.html", {
|
|
"request": request,
|
|
"logs": logs,
|
|
"exercise_id": exercise_id,
|
|
"workout_day_id": workout_day_id,
|
|
"next_set": next_set,
|
|
"session_id": ws.id,
|
|
"suggested_reps": suggestion.get("suggested_reps"),
|
|
"suggested_weight": suggestion.get("suggested_weight"),
|
|
})
|
|
|
|
|
|
@router.post("/{log_id}/edit", response_class=HTMLResponse)
|
|
async def edit_log(
|
|
log_id: int,
|
|
request: Request,
|
|
session: Session = Depends(get_db_session),
|
|
admin: User = Depends(get_current_admin_user),
|
|
):
|
|
"""Edit an existing log entry.
|
|
|
|
Args:
|
|
log_id: The log entry ID.
|
|
request: The incoming HTTP request.
|
|
session: Database session.
|
|
admin: The authenticated admin user.
|
|
|
|
Returns:
|
|
Rendered updated log entry partial.
|
|
"""
|
|
form = await request.form()
|
|
log_service = LogService(session)
|
|
|
|
log_service.update_log(
|
|
log_id,
|
|
reps_completed=int(form.get("reps", 0)),
|
|
weight_used=form.get("weight", ""),
|
|
felt_easy=form.get("felt_easy") == "on",
|
|
notes=form.get("notes"),
|
|
)
|
|
|
|
log = log_service.get_log_by_id(log_id)
|
|
logs = log_service.list_logs_for_exercise(log.session_id, log.exercise_id)
|
|
next_set = len(logs) + 1
|
|
|
|
# Fetch suggestion for pre-filling the next set form
|
|
active_profile_id = get_active_profile_id(request)
|
|
suggestion = {}
|
|
if active_profile_id:
|
|
progression = ProgressionService(session)
|
|
suggestion = progression.get_suggestion(active_profile_id, log.exercise_id)
|
|
|
|
templates = request.app.state.templates
|
|
return templates.TemplateResponse("partials/log_entry.html", {
|
|
"request": request,
|
|
"logs": logs,
|
|
"exercise_id": log.exercise_id,
|
|
"workout_day_id": 0,
|
|
"next_set": next_set,
|
|
"session_id": log.session_id,
|
|
"suggested_reps": suggestion.get("suggested_reps"),
|
|
"suggested_weight": suggestion.get("suggested_weight"),
|
|
})
|
|
|
|
|
|
@router.post("/{log_id}/delete", response_class=HTMLResponse)
|
|
async def delete_log(
|
|
log_id: int,
|
|
request: Request,
|
|
session: Session = Depends(get_db_session),
|
|
admin: User = Depends(get_current_admin_user),
|
|
):
|
|
"""Delete a log entry.
|
|
|
|
Args:
|
|
log_id: The log entry ID.
|
|
request: The incoming HTTP request.
|
|
session: Database session.
|
|
admin: The authenticated admin user.
|
|
|
|
Returns:
|
|
Rendered updated log entries partial.
|
|
"""
|
|
log_service = LogService(session)
|
|
log = log_service.get_log_by_id(log_id)
|
|
|
|
if log:
|
|
exercise_id = log.exercise_id
|
|
session_id = log.session_id
|
|
log_service.delete_log(log_id)
|
|
|
|
logs = log_service.list_logs_for_exercise(session_id, exercise_id)
|
|
next_set = len(logs) + 1
|
|
|
|
# Fetch suggestion for pre-filling the next set form
|
|
active_profile_id = get_active_profile_id(request)
|
|
suggestion = {}
|
|
if active_profile_id:
|
|
progression = ProgressionService(session)
|
|
suggestion = progression.get_suggestion(active_profile_id, exercise_id)
|
|
|
|
templates = request.app.state.templates
|
|
return templates.TemplateResponse("partials/log_entry.html", {
|
|
"request": request,
|
|
"logs": logs,
|
|
"exercise_id": exercise_id,
|
|
"workout_day_id": 0,
|
|
"next_set": next_set,
|
|
"session_id": session_id,
|
|
"suggested_reps": suggestion.get("suggested_reps"),
|
|
"suggested_weight": suggestion.get("suggested_weight"),
|
|
})
|
|
|
|
return HTMLResponse("")
|