Files
krafttrainer/backend/internal/handler/handler.go
Christoph K. a954f2c59d Add multi-user support with export feature
- New users table (migration 004) with user_id on exercises, training_sets, sessions
- User CRUD endpoints (GET/POST /api/v1/users, DELETE /api/v1/users/{id})
- All existing endpoints scoped to X-User-ID header
- CSV export endpoint (GET /api/v1/export) for completed sessions
- UserGate in PageShell: blocks app until a user is selected
- Settings page for managing users (create, switch, delete)
- BottomNav/Sidebar updated with settings navigation
- Fix: nil pointer panic in handleDeleteUser on success path
- Fix: export download now uses fetch with X-User-ID header instead of window.location.href

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:55:51 +01:00

106 lines
3.1 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("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)
// 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
}