Files
krafttrainer/backend/internal/handler/image_handler.go
Christoph K. 063aa67615 Add exercise numbers, image uploads, version display, session resume, and training sparklines
- 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>
2026-03-27 08:37:29 +01:00

136 lines
3.5 KiB
Go

package handler
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"github.com/google/uuid"
)
const (
maxImageSize = 5 << 20 // 5 MB
uploadDir = "uploads"
)
func (h *Handler) handleListImages(w http.ResponseWriter, r *http.Request) {
exerciseID, err := pathID(r, "id")
if err != nil {
writeError(w, http.StatusBadRequest, "Ungültige ID")
return
}
images, err := h.store.ListExerciseImages(exerciseID)
if err != nil {
writeError(w, http.StatusInternalServerError, "Fehler beim Laden der Bilder")
return
}
writeJSON(w, http.StatusOK, images)
}
func (h *Handler) handleUploadImage(w http.ResponseWriter, r *http.Request) {
exerciseID, err := pathID(r, "id")
if err != nil {
writeError(w, http.StatusBadRequest, "Ungültige ID")
return
}
// Übung muss existieren
exercise, err := h.store.GetExercise(exerciseID)
if err != nil {
writeError(w, http.StatusInternalServerError, "Fehler beim Prüfen der Übung")
return
}
if exercise == nil {
writeError(w, http.StatusNotFound, "Übung nicht gefunden")
return
}
r.Body = http.MaxBytesReader(w, r.Body, maxImageSize)
if err := r.ParseMultipartForm(maxImageSize); err != nil {
writeError(w, http.StatusBadRequest, "Datei zu groß (max 5 MB)")
return
}
file, header, err := r.FormFile("image")
if err != nil {
writeError(w, http.StatusBadRequest, "Kein Bild im Request")
return
}
defer file.Close()
// Content-Type prüfen
ct := header.Header.Get("Content-Type")
if ct != "image/jpeg" {
writeError(w, http.StatusBadRequest, "Nur JPG-Bilder erlaubt")
return
}
// Datei speichern
filename := uuid.New().String() + ".jpg"
destPath := filepath.Join(uploadDir, filename)
if err := os.MkdirAll(uploadDir, 0755); err != nil {
writeError(w, http.StatusInternalServerError, "Fehler beim Erstellen des Upload-Verzeichnisses")
return
}
dst, err := os.Create(destPath)
if err != nil {
writeError(w, http.StatusInternalServerError, "Fehler beim Speichern der Datei")
return
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
os.Remove(destPath)
writeError(w, http.StatusInternalServerError, "Fehler beim Schreiben der Datei")
return
}
img, err := h.store.CreateExerciseImage(exerciseID, filename)
if err != nil {
os.Remove(destPath)
writeError(w, http.StatusInternalServerError, "Fehler beim Speichern des Bildes")
return
}
writeJSON(w, http.StatusCreated, img)
}
func (h *Handler) handleDeleteImage(w http.ResponseWriter, r *http.Request) {
imageID, err := pathID(r, "imageId")
if err != nil {
writeError(w, http.StatusBadRequest, "Ungültige Bild-ID")
return
}
filename, err := h.store.DeleteExerciseImage(imageID)
if err != nil {
writeError(w, http.StatusNotFound, "Bild nicht gefunden")
return
}
// Datei vom Dateisystem löschen
os.Remove(filepath.Join(uploadDir, filename))
w.WriteHeader(http.StatusNoContent)
}
// RegisterImageRoutes registriert die Bild-spezifischen Routen und den statischen File-Server.
func (h *Handler) RegisterImageRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/v1/exercises/{id}/images", h.handleListImages)
mux.HandleFunc("POST /api/v1/exercises/{id}/images", h.handleUploadImage)
mux.HandleFunc("DELETE /api/v1/exercises/{id}/images/{imageId}", h.handleDeleteImage)
// Bilder statisch servieren
mux.Handle("GET /uploads/", http.StripPrefix("/uploads/",
http.FileServer(http.Dir(uploadDir))))
// uploads-Verzeichnis sicherstellen
os.MkdirAll(uploadDir, 0755)
fmt.Println("Bild-Upload konfiguriert:", uploadDir)
}