from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session from tracker.config import load_config from tracker.config_updates import patch_active_program_id from tracker.db import get_session from tracker.training_schemas import ( AddProgramExerciseInput, CreateProgramInput, DisplaySettingsResponse, ExerciseResponse, HistoryResponse, LogRunInput, LogWorkoutInput, ProgramDetailResponse, ProgramExerciseDetail, ProgramSummary, RunLogEntry, SetActiveProgramInput, TodayResponse, UpdateProgramExerciseInput, WeightInput, ) from tracker.training_service import ( add_exercise_to_program, create_program_from_input, fetch_active_program_detail, fetch_history, fetch_runs, fetch_today, list_exercise_catalog, list_program_summaries, log_body_weight, log_running_session, log_workout_session, remove_program_exercise, update_program_exercise, ) router = APIRouter(prefix="/api") @router.get("/meta/display") def get_display_settings() -> DisplaySettingsResponse: cfg = load_config() return DisplaySettingsResponse( weight_unit=cfg.display_weight_unit, distance_unit=cfg.display_distance_unit, ) @router.get("/today") def get_today( day_label: str, program_id: int | None = None, session: Session = Depends(get_session), ) -> TodayResponse: cfg = load_config() data, err = fetch_today(session, cfg, day_label, program_id) if err is not None: raise HTTPException(status_code=404, detail=err) if data is None: raise HTTPException(status_code=404, detail="No program found") return data @router.post("/log") def log_workout( body: LogWorkoutInput, session: Session = Depends(get_session), ) -> dict[str, int]: logged_count = log_workout_session(session, body) return {"logged": logged_count} @router.post("/weight") def log_weight( body: WeightInput, session: Session = Depends(get_session), ) -> dict[str, float]: weight_kg = log_body_weight(session, body) return {"weight_kg": weight_kg} @router.get("/history") def get_history( days: int = 30, session: Session = Depends(get_session), ) -> HistoryResponse: return fetch_history(session, days) @router.post("/log-run") def log_run( body: LogRunInput, session: Session = Depends(get_session), ) -> dict[str, int]: run_log_id, err = log_running_session(session, body) if err is not None: raise HTTPException(status_code=404, detail=err) if run_log_id is None: raise HTTPException(status_code=500, detail="Run log failed") return {"run_log_id": run_log_id} @router.get("/runs") def get_runs( days: int = 30, program_id: int | None = None, day_label: str | None = None, session: Session = Depends(get_session), ) -> list[RunLogEntry]: return fetch_runs(session, days, program_id, day_label) @router.get("/programs") def list_programs( session: Session = Depends(get_session), ) -> list[ProgramSummary]: return list_program_summaries(session) @router.post("/programs") def create_program( body: CreateProgramInput, session: Session = Depends(get_session), ) -> ProgramSummary: return create_program_from_input(session, body) @router.get("/programs/active") def get_active_program( session: Session = Depends(get_session), ) -> ProgramDetailResponse: cfg = load_config() data, err = fetch_active_program_detail(session, cfg) if err is not None: raise HTTPException(status_code=404, detail=err) if data is None: raise HTTPException(status_code=404, detail="No program found") return data @router.put("/programs/active") def set_active_program(body: SetActiveProgramInput) -> dict[str, int | None]: patch_active_program_id(body.program_id) return {"active_program_id": body.program_id} @router.get("/exercises") def list_exercises( session: Session = Depends(get_session), ) -> list[ExerciseResponse]: return list_exercise_catalog(session) @router.post("/programs/{program_id}/exercises") def add_program_exercise( program_id: int, body: AddProgramExerciseInput, session: Session = Depends(get_session), ) -> ProgramExerciseDetail: data, err = add_exercise_to_program(session, program_id, body) if err is not None: raise HTTPException(status_code=404, detail=err) if data is None: raise HTTPException(status_code=404, detail="Not found") return data @router.delete("/programs/{program_id}/exercises/{exercise_id}") def remove_program_exercise_route( program_id: int, exercise_id: int, day_label: str, session: Session = Depends(get_session), ) -> dict[str, str]: err = remove_program_exercise(session, program_id, exercise_id, day_label) if err is not None: raise HTTPException(status_code=404, detail=err) return {"removed": f"exercise {exercise_id} from day {day_label}"} @router.put("/programs/{program_id}/exercises/{exercise_id}") def update_program_exercise_route( program_id: int, exercise_id: int, day_label: str, body: UpdateProgramExerciseInput, session: Session = Depends(get_session), ) -> ProgramExerciseDetail: data, err = update_program_exercise(session, program_id, exercise_id, day_label, body) if err is not None: raise HTTPException(status_code=404, detail=err) if data is None: raise HTTPException(status_code=404, detail="Not found") return data