Files
krafttrainer/backend/cmd/server/main.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

78 lines
1.8 KiB
Go
Executable File

package main
import (
"io/fs"
"log"
"net/http"
"krafttrainer/internal/handler"
mig "krafttrainer/internal/migrate"
"krafttrainer/internal/store"
"krafttrainer/migrations"
"krafttrainer/static"
)
// Version wird beim Build per ldflags gesetzt.
var Version = "dev"
func main() {
// Datenbank initialisieren
s, err := store.New("krafttrainer.db")
if err != nil {
log.Fatalf("Datenbank: %v", err)
}
defer s.Close()
// Migrationen ausführen
if err := mig.Run(s.DB(), migrations.FS); err != nil {
log.Fatalf("Migrationen: %v", err)
}
log.Println("Migrationen erfolgreich")
// HTTP-Routen
mux := http.NewServeMux()
h := handler.New(s)
h.RegisterRoutes(mux)
h.RegisterImageRoutes(mux)
// Version-Endpoint
mux.HandleFunc("GET /api/v1/version", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"version":"` + Version + `"}`))
})
// SPA-Fallback: statische Dateien aus embed.FS servieren
mux.Handle("/", spaHandler(static.FS))
// Middleware-Chain und Server starten
srv := handler.Chain(mux, handler.Recoverer, handler.RequestLogger, handler.CORS)
log.Println("Server startet auf :8090")
if err := http.ListenAndServe(":8090", srv); err != nil {
log.Fatalf("Server: %v", err)
}
}
// spaHandler serviert statische Dateien und fällt auf index.html zurück (SPA-Routing).
func spaHandler(embeddedFS fs.FS) http.Handler {
fileServer := http.FileServer(http.FS(embeddedFS))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if path == "/" {
path = "index.html"
} else if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
f, err := embeddedFS.Open(path)
if err != nil {
// SPA-Fallback: index.html für unbekannte Pfade
r.URL.Path = "/"
} else {
f.Close()
}
fileServer.ServeHTTP(w, r)
})
}