from datetime import date from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.orm import Session, selectinload from app.auth import get_current_user from app.database import get_db from app.models.workout import Exercise, Workout, WorkoutSet router = APIRouter( prefix="/api", tags=["workouts"], dependencies=[Depends(get_current_user)] ) # --- Schemas --- class ExerciseCreate(BaseModel): name: str category: str class ExerciseRead(ExerciseCreate): id: int model_config = {"from_attributes": True} class WorkoutSetCreate(BaseModel): exercise_id: int set_number: int reps: int | None = None weight_lbs: float | None = None duration_min: float | None = None class WorkoutSetRead(WorkoutSetCreate): id: int model_config = {"from_attributes": True} class WorkoutCreate(BaseModel): date: date name: str | None = None notes: str | None = None sets: list[WorkoutSetCreate] = [] class WorkoutRead(BaseModel): id: int date: date name: str | None notes: str | None sets: list[WorkoutSetRead] model_config = {"from_attributes": True} # --- Exercise endpoints --- @router.get("/exercises", response_model=list[ExerciseRead]) def list_exercises(db: Session = Depends(get_db)): return db.scalars(select(Exercise).order_by(Exercise.name)).all() @router.post("/exercises", response_model=ExerciseRead, status_code=201) def create_exercise(body: ExerciseCreate, db: Session = Depends(get_db)): ex = Exercise(**body.model_dump()) db.add(ex) db.commit() db.refresh(ex) return ex # --- Workout endpoints --- @router.get("/workouts", response_model=list[WorkoutRead]) def list_workouts( from_date: date | None = Query(None, alias="from"), to_date: date | None = Query(None, alias="to"), db: Session = Depends(get_db), ): q = ( select(Workout) .options(selectinload(Workout.sets)) .order_by(Workout.date.desc()) ) if from_date: q = q.where(Workout.date >= from_date) if to_date: q = q.where(Workout.date <= to_date) return db.scalars(q).all() @router.post("/workouts", response_model=WorkoutRead, status_code=201) def create_workout(body: WorkoutCreate, db: Session = Depends(get_db)): sets_data = body.sets workout = Workout( date=body.date, name=body.name, notes=body.notes, sets=[WorkoutSet(**s.model_dump()) for s in sets_data], ) db.add(workout) db.commit() db.refresh(workout) return workout @router.put("/workouts/{workout_id}", response_model=WorkoutRead) def update_workout( workout_id: int, body: WorkoutCreate, db: Session = Depends(get_db) ): workout = db.get(Workout, workout_id, options=[selectinload(Workout.sets)]) if not workout: raise HTTPException(status_code=404, detail="Workout not found") workout.date = body.date workout.name = body.name workout.notes = body.notes # Replace sets workout.sets.clear() for s in body.sets: workout.sets.append(WorkoutSet(**s.model_dump())) db.commit() db.refresh(workout) return workout @router.delete("/workouts/{workout_id}", status_code=204) def delete_workout(workout_id: int, db: Session = Depends(get_db)): workout = db.get(Workout, workout_id) if not workout: raise HTTPException(status_code=404, detail="Workout not found") db.delete(workout) db.commit()