package commands import ( "context" "fmt" "os" "path/filepath" "strings" "time" "github.com/sirupsen/logrus" "github.com/urfave/cli" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/migrate" "github.com/photoprism/photoprism/pkg/report" ) var MigrationsStatusCommand = cli.Command{ Name: "ls", Aliases: []string{"status", "show"}, Usage: "Displays the status of schema migrations", ArgsUsage: "[migrations...]", Flags: report.CliFlags, Action: migrationsStatusAction, } var MigrationsRunCommand = cli.Command{ Name: "run", Aliases: []string{"execute", "migrate"}, Usage: "Executes database schema migrations", ArgsUsage: "[migrations...]", Flags: []cli.Flag{ cli.BoolFlag{ Name: "failed, f", Usage: "run previously failed migrations", }, cli.BoolFlag{ Name: "trace, t", Usage: "show trace logs for debugging", }, }, Action: migrationsRunAction, } // MigrationsCommand registers the "migrations" CLI command. var MigrationsCommand = cli.Command{ Name: "migrations", Usage: "Database schema migration subcommands", Subcommands: []cli.Command{ MigrationsStatusCommand, MigrationsRunCommand, }, } // migrationsStatusAction lists the status of schema migration. func migrationsStatusAction(ctx *cli.Context) error { conf, err := InitConfig(ctx) _, cancel := context.WithCancel(context.Background()) defer cancel() if err != nil { return err } conf.RegisterDb() defer conf.Shutdown() var ids []string // Check argument for specific migrations to be run. if migrations := strings.TrimSpace(ctx.Args().First()); migrations != "" { ids = strings.Fields(migrations) } db := conf.Db() status, err := migrate.Status(db, ids) if err != nil { return err } // Report columns. cols := []string{"ID", "Dialect", "Stage", "Started At", "Finished At", "Status"} // Report rows. rows := make([][]string, 0, len(status)) for _, m := range status { var stage, started, finished, info string if m.Stage == "" { stage = "main" } else { stage = m.Stage } if m.StartedAt.IsZero() { started = "-" } else { started = m.StartedAt.Format("2006-01-02 15:04:05") } if m.Finished() { finished = m.FinishedAt.Format("2006-01-02 15:04:05") } else { finished = "-" } if m.Error != "" { info = m.Error } else if m.Finished() { info = "OK" } else if m.StartedAt.IsZero() { info = "-" } else if m.Repeat(false) { info = "Repeat" } else { info = "Running?" } rows = append(rows, []string{m.ID, m.Dialect, stage, started, finished, info}) } // Display report. info, err := report.RenderFormat(rows, cols, report.CliFormat(ctx)) if err != nil { return err } fmt.Println(info) return nil } // migrationsRunAction executes database schema migrations. func migrationsRunAction(ctx *cli.Context) error { if ctx.Args().First() == "ls" { return fmt.Errorf("run '%s migrations ls' to display the status of schema migrations", filepath.Base(os.Args[0])) } start := time.Now() conf := config.NewConfig(ctx) _, cancel := context.WithCancel(context.Background()) defer cancel() if err := conf.Init(); err != nil { return err } conf.RegisterDb() defer conf.Shutdown() if ctx.Bool("trace") { log.SetLevel(logrus.TraceLevel) log.Infoln("migrate: enabled trace mode") } runFailed := ctx.Bool("failed") if runFailed { log.Infoln("migrate: running previously failed migrations") } var ids []string // Check argument for specific migrations to be run. if migrations := strings.TrimSpace(ctx.Args().First()); migrations != "" { ids = strings.Fields(migrations) } log.Infoln("migrating database schema...") // Run migrations. conf.MigrateDb(runFailed, ids) elapsed := time.Since(start) log.Infof("completed in %s", elapsed) return nil }