Files
SneakySwole/app/routes/logging.py
Phillip Tarrant 576d3bbb68 feat: replace admin auth with cookie-based profile picker
Remove all authentication (login, sessions, bcrypt, itsdangerous) since
the app runs on a private homelab LAN. Replace with a profile picker
landing page and cookie-based profile selection (1-year expiry).

- Add Alembic migration to drop password_hash/is_admin columns
- Delete auth service, auth routes, login template, and auth tests
- Rewrite app/utils/auth.py with NoProfileSelectedError and
  require_active_profile dependency
- Add profile creation flow (GET/POST /profiles/create)
- Rewrite home page as profile picker with card layout
- Update all route files to use profile dependency instead of admin auth
- Remove bcrypt and itsdangerous from requirements
- Remove admin_username/admin_password from config
- Update all tests for new profile-based access model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:40:54 -05:00

142 lines
4.2 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.workout_session_service import WorkoutSessionService
from app.utils.auth import require_active_profile, 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),
profile: User = Depends(require_active_profile),
):
"""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.
"""
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"
# Get or create today's session
ws_service = WorkoutSessionService(session)
ws = ws_service.get_or_create_session(
user_id=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
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,
})
@router.post("/{log_id}/edit", response_class=HTMLResponse)
async def edit_log(
log_id: int,
request: Request,
session: Session = Depends(get_db_session),
profile: User = Depends(require_active_profile),
):
"""Edit an existing log entry."""
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
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,
})
@router.post("/{log_id}/delete", response_class=HTMLResponse)
async def delete_log(
log_id: int,
request: Request,
session: Session = Depends(get_db_session),
profile: User = Depends(require_active_profile),
):
"""Delete a log entry."""
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
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,
})
return HTMLResponse("")