diff --git a/cmd/wt-debug/cli.go b/cmd/wt-debug/cli.go index 57e524b8..4f8db44d 100644 --- a/cmd/wt-debug/cli.go +++ b/cmd/wt-debug/cli.go @@ -27,6 +27,8 @@ func newCLI() (*cli, error) { return nil, err } + a.ConfigureLogger() + if err := a.ConfigureDatabase(); err != nil { return nil, err } diff --git a/cmd/wt-debug/workouts.go b/cmd/wt-debug/workouts.go index 386f6231..49f93fa5 100644 --- a/cmd/wt-debug/workouts.go +++ b/cmd/wt-debug/workouts.go @@ -1,7 +1,10 @@ package main import ( + "encoding/json" + "fmt" "os" + "slices" "strconv" "strings" @@ -19,6 +22,8 @@ func (c *cli) workoutsCmd() *cobra.Command { cmd.AddCommand(c.workoutsListCmd()) cmd.AddCommand(c.workoutsDiagCmd()) cmd.AddCommand(c.workoutsShowCmd()) + cmd.AddCommand(c.workoutsExportCmd()) + cmd.AddCommand(c.workoutsImportCmd()) return cmd } @@ -85,7 +90,7 @@ func (c *cli) workoutsListCmd() *cobra.Command { func (c *cli) workoutsShowCmd() *cobra.Command { return &cobra.Command{ - Use: "show", + Use: "show workout-id", Short: "Show information about a workout", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -116,3 +121,80 @@ func (c *cli) workoutsShowCmd() *cobra.Command { }, } } + +func (c *cli) workoutsExportCmd() *cobra.Command { + return &cobra.Command{ + Use: "export [workout-id...]", + Short: "Export all, or some workouts to json (stdout)", + RunE: func(cmd *cobra.Command, args []string) error { + var ids []uint64 + + if err := c.getDatabase().Model(&database.Workout{}).Pluck("ID", &ids).Error; err != nil { + return err + } + + filter := len(args) > 0 + + for _, id := range ids { + if filter && !slices.Contains(args, strconv.FormatUint(id, 10)) { + continue + } + + c.app.Logger().Info("Exporting workout", "id", id) + + wo, err := database.GetWorkoutDetails(c.getDatabase(), id) + if err != nil { + return err + } + + e, err := wo.Export() + if err != nil { + return err + } + + os.Stdout.Write(e) + } + + return nil + }, + } +} + +func (c *cli) workoutsImportCmd() *cobra.Command { + return &cobra.Command{ + Use: "import ", + Short: "Import workouts from a json file (eg. from an earlier export)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + filename := args[0] + c.app.Logger().Info(fmt.Sprintf("Importing from '%s'", filename)) + + dat, err := os.ReadFile(filename) + if err != nil { + return err + } + + lines := strings.Split(string(dat), "\n") + + for _, line := range lines { + if line == "" { + continue + } + + var wo database.Workout + + if err := json.Unmarshal([]byte(line), &wo); err != nil { + return err + } + + c.app.Logger().Info("Importing workout", "id", wo.ID) + + if err := wo.Save(c.app.DB()); err != nil { + return err + } + } + + return nil + }, + } +} diff --git a/pkg/app/app.go b/pkg/app/app.go index c0201610..51dc707f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -122,9 +122,9 @@ func newLogger(enabled bool) *slog.Logger { } func newLogHandler() slog.Handler { - w := os.Stdout + w := os.Stderr if isatty.IsTerminal(w.Fd()) { - return tint.NewHandler(os.Stdout, &tint.Options{ + return tint.NewHandler(os.Stderr, &tint.Options{ Level: slog.LevelDebug, TimeFormat: time.Kitchen, }) @@ -173,3 +173,7 @@ func (a *App) createAdminUser() error { func (a *App) DB() *gorm.DB { return a.db } + +func (a *App) Logger() *slog.Logger { + return a.logger +} diff --git a/pkg/app/background.go b/pkg/app/background.go index e7ba0cea..262e12cc 100644 --- a/pkg/app/background.go +++ b/pkg/app/background.go @@ -264,7 +264,7 @@ func (a *App) updateWorkout(l *slog.Logger) { } func (a *App) UpdateWorkout(i uint64) error { - w, err := database.GetWorkoutWithGPX(a.db, i) + w, err := database.GetWorkoutDetails(a.db, i) if err != nil { return err } diff --git a/pkg/database/workouts.go b/pkg/database/workouts.go index 2f061987..0a7cd0f6 100644 --- a/pkg/database/workouts.go +++ b/pkg/database/workouts.go @@ -1,7 +1,9 @@ package database import ( + "bytes" "crypto/sha256" + "encoding/json" "errors" "path/filepath" "slices" @@ -411,20 +413,12 @@ func GetWorkouts(db *gorm.DB) ([]*Workout, error) { return w, nil } -func GetWorkoutWithGPXByUUID(db *gorm.DB, u uuid.UUID) (*Workout, error) { - return GetWorkoutByUUID(db.Preload("GPX"), u) -} - -func GetWorkoutWithGPX(db *gorm.DB, id uint64) (*Workout, error) { - return GetWorkout(db.Preload("GPX").Preload("Data.Details"), id) -} - func GetWorkoutDetailsByUUID(db *gorm.DB, u uuid.UUID) (*Workout, error) { - return GetWorkoutWithGPXByUUID(db.Preload("Data.Details"), u) + return GetWorkoutByUUID(db.Preload("GPX").Preload("Data.Details"), u) } func GetWorkoutDetails(db *gorm.DB, id uint64) (*Workout, error) { - return GetWorkoutWithGPX(db.Preload("Data.Details"), id) + return GetWorkout(db.Preload("GPX").Preload("Data.Details"), id) } func GetWorkoutByUUID(db *gorm.DB, u uuid.UUID) (*Workout, error) { @@ -673,3 +667,13 @@ func (w *Workout) EquipmentIDs() []uint64 { func (w *Workout) Uses(e Equipment) bool { return slices.Contains(w.EquipmentIDs(), e.ID) } + +func (w *Workout) Export() ([]byte, error) { + var buf bytes.Buffer + + if err := json.NewEncoder(&buf).Encode(w); err != nil { + return nil, err + } + + return buf.Bytes(), nil +}