- Exercise number (UF#): optional field on exercises, displayed in cards, training, and sets - Import training plan numbers via migration 005 (UPDATE by name) - Exercise images: JPG upload with multi-image support per exercise (migration 006) - Version endpoint (GET /api/v1/version) with ldflags injection in Makefile and Dockerfile - Version displayed on settings page - Session resume: GET /api/v1/sessions/active endpoint, auto-resume on training page load - Block new session while one is active (409 Conflict) - e1RM sparkline chart per exercise during training (Epley formula) - Fix CORS: add X-User-ID to allowed headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
108 lines
3.2 KiB
Go
Executable File
108 lines
3.2 KiB
Go
Executable File
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"krafttrainer/internal/store"
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// Handler bündelt alle HTTP-Handler und hält eine Referenz auf den Store.
|
|
type Handler struct {
|
|
store *store.Store
|
|
}
|
|
|
|
// New erstellt einen neuen Handler.
|
|
func New(store *store.Store) *Handler {
|
|
return &Handler{store: store}
|
|
}
|
|
|
|
// RegisterRoutes registriert alle API-Routen am ServeMux.
|
|
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
|
// Users (kein X-User-ID Header nötig)
|
|
mux.HandleFunc("GET /api/v1/users", h.handleListUsers)
|
|
mux.HandleFunc("POST /api/v1/users", h.handleCreateUser)
|
|
mux.HandleFunc("DELETE /api/v1/users/{id}", h.handleDeleteUser)
|
|
|
|
// Exercises
|
|
mux.HandleFunc("GET /api/v1/exercises", h.handleListExercises)
|
|
mux.HandleFunc("POST /api/v1/exercises", h.handleCreateExercise)
|
|
mux.HandleFunc("PUT /api/v1/exercises/{id}", h.handleUpdateExercise)
|
|
mux.HandleFunc("DELETE /api/v1/exercises/{id}", h.handleDeleteExercise)
|
|
|
|
// Training Sets
|
|
mux.HandleFunc("GET /api/v1/sets", h.handleListSets)
|
|
mux.HandleFunc("POST /api/v1/sets", h.handleCreateSet)
|
|
mux.HandleFunc("PUT /api/v1/sets/{id}", h.handleUpdateSet)
|
|
mux.HandleFunc("DELETE /api/v1/sets/{id}", h.handleDeleteSet)
|
|
|
|
// Sessions
|
|
mux.HandleFunc("GET /api/v1/sessions/active", h.handleGetActiveSession)
|
|
mux.HandleFunc("POST /api/v1/sessions", h.handleCreateSession)
|
|
mux.HandleFunc("GET /api/v1/sessions", h.handleListSessions)
|
|
mux.HandleFunc("GET /api/v1/sessions/{id}", h.handleGetSession)
|
|
mux.HandleFunc("PUT /api/v1/sessions/{id}/end", h.handleEndSession)
|
|
mux.HandleFunc("DELETE /api/v1/sessions/{id}", h.handleDeleteSession)
|
|
|
|
// Session Logs
|
|
mux.HandleFunc("POST /api/v1/sessions/{id}/logs", h.handleCreateLog)
|
|
mux.HandleFunc("PUT /api/v1/sessions/{id}/logs/{logId}", h.handleUpdateLog)
|
|
mux.HandleFunc("DELETE /api/v1/sessions/{id}/logs/{logId}", h.handleDeleteLog)
|
|
|
|
// Stats
|
|
mux.HandleFunc("GET /api/v1/exercises/{id}/last-log", h.handleGetLastLog)
|
|
mux.HandleFunc("GET /api/v1/exercises/{id}/history", h.handleGetExerciseHistory)
|
|
mux.HandleFunc("GET /api/v1/stats/overview", h.handleGetStatsOverview)
|
|
|
|
// Export
|
|
mux.HandleFunc("GET /api/v1/export", h.handleExport)
|
|
}
|
|
|
|
// --- Hilfsfunktionen ---
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, data any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, message string) {
|
|
writeJSON(w, status, map[string]string{"error": message})
|
|
}
|
|
|
|
func decodeJSON(r *http.Request, dst any) error {
|
|
dec := json.NewDecoder(r.Body)
|
|
dec.DisallowUnknownFields()
|
|
return dec.Decode(dst)
|
|
}
|
|
|
|
func pathID(r *http.Request, name string) (int64, error) {
|
|
return strconv.ParseInt(r.PathValue(name), 10, 64)
|
|
}
|
|
|
|
func queryInt(r *http.Request, name string, defaultVal int) int {
|
|
v := r.URL.Query().Get(name)
|
|
if v == "" {
|
|
return defaultVal
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return defaultVal
|
|
}
|
|
return n
|
|
}
|
|
|
|
// userID liest die X-User-ID aus dem Request-Header.
|
|
func userID(r *http.Request) (int64, error) {
|
|
v := r.Header.Get("X-User-ID")
|
|
if v == "" {
|
|
return 0, errors.New("X-User-ID Header fehlt")
|
|
}
|
|
id, err := strconv.ParseInt(v, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
return 0, errors.New("ungültige User-ID")
|
|
}
|
|
return id, nil
|
|
}
|