Log messages are lower-case event names with their details as key/value
attributes ("error" for errors), in place of formatted strings.
Loggable package take a WithLogger option as part of their "New*" call,
defaulting to slog.Default(). Each tags its lines with its `component`.
Some debug loggers print directly rather than using slog when in
terminal logging mode.
96 lines
2.4 KiB
Go
96 lines
2.4 KiB
Go
package migrate
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"log/slog"
|
|
"path/filepath"
|
|
"text/tabwriter"
|
|
|
|
"github.com/pressly/goose/v3"
|
|
"github.com/spf13/cobra"
|
|
|
|
"git.avsdev.uk/Go/job-search/internal/cli"
|
|
"git.avsdev.uk/Go/job-search/internal/store"
|
|
)
|
|
|
|
// printStatus lists every migration as applied (with when) or pending,
|
|
// after a summary line, e.g.
|
|
//
|
|
// Database version 4 of 6 (2 pending)
|
|
// 1 applied 2026-10-07 10:12:01 00001_create_opportunities_table.sql
|
|
// ...
|
|
// 5 pending 00005_create_opportunity_classifications_table.sql
|
|
func printStatus(ctx context.Context, w io.Writer, m *goose.Provider) error {
|
|
statuses, err := m.Status(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
current, err := m.GetDBVersion(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
latest := store.LatestVersion(m)
|
|
|
|
pending := 0
|
|
for _, s := range statuses {
|
|
if s.State == goose.StatePending {
|
|
pending++
|
|
}
|
|
}
|
|
|
|
ew := &errWriter{w: w}
|
|
switch {
|
|
case current > latest:
|
|
ew.printf("Database version %d is newer than this build (latest %d)\n",
|
|
current, latest)
|
|
case pending == 0:
|
|
ew.printf("Database version %d of %d (up-to-date)\n", current, latest)
|
|
default:
|
|
ew.printf("Database version %d of %d (%d pending)\n", current, latest, pending)
|
|
}
|
|
if ew.err != nil {
|
|
return ew.err
|
|
}
|
|
|
|
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
|
|
ew = &errWriter{w: tw}
|
|
for _, s := range statuses {
|
|
applied := ""
|
|
if s.State == goose.StateApplied {
|
|
applied = s.AppliedAt.Local().Format("2006-01-02 15:04:05")
|
|
}
|
|
ew.printf(" %d\t%s\t%s\t%s\n",
|
|
s.Source.Version, s.State, applied, filepath.Base(s.Source.Path))
|
|
}
|
|
return errors.Join(ew.err, tw.Flush())
|
|
}
|
|
|
|
func commandStatus(opts *cli.Options) *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show which migrations have been applied to the SQLite database",
|
|
Args: cobra.NoArgs,
|
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
slog.Debug("command persistent pre-run", "command", "migrate.status")
|
|
},
|
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
|
slog.Debug("command persistent post-run", "command", "migrate.status")
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
|
slog.Debug("command run", "command", "migrate.status")
|
|
|
|
m, db, err := openMigrator(cmd, opts, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeDB(db, &err)
|
|
|
|
return printStatus(cmd.Context(), cmd.OutOrStdout(), m)
|
|
},
|
|
}
|
|
}
|